From 35e4f1d4488bddd16c5b54dc989c5667b9ca6de3 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 10:46:14 -0200 Subject: [PATCH 01/37] Project Setup --- .gitignore | 24 + .../DesafioIOS.xcodeproj/project.pbxproj | 679 ++++++++++++++++++ DesafioIOS/DesafioIOS/AppDelegate.swift | 93 +++ .../AppIcon.appiconset/Contents.json | 53 ++ .../DesafioIOS/Assets.xcassets/Contents.json | 6 + .../avatar_noimage.imageset/Contents.json | 21 + .../avatar_noimage@2x.png | Bin 0 -> 5176 bytes .../ic_back.imageset/Contents.json | 22 + .../ic_back.imageset/ic_back@2x.png | Bin 0 -> 1317 bytes .../ic_back.imageset/ic_back@3x.png | Bin 0 -> 1670 bytes .../ic_close.imageset/Contents.json | 22 + .../ic_close.imageset/ic_close@2x.png | Bin 0 -> 1566 bytes .../ic_close.imageset/ic_close@3x.png | Bin 0 -> 1677 bytes .../ic_fork.imageset/Contents.json | 22 + .../ic_fork.imageset/ic_fork@2x.png | Bin 0 -> 1532 bytes .../ic_fork.imageset/ic_fork@3x.png | Bin 0 -> 1792 bytes .../ic_menu.imageset/Contents.json | 22 + .../ic_menu.imageset/ic_menu@2x.png | Bin 0 -> 1098 bytes .../ic_menu.imageset/ic_menu@3x.png | Bin 0 -> 1180 bytes .../ic_reload.imageset/Contents.json | 22 + .../ic_reload.imageset/ic_reload@2x.png | Bin 0 -> 1706 bytes .../ic_reload.imageset/ic_reload@3x.png | Bin 0 -> 2126 bytes .../ic_star.imageset/Contents.json | 22 + .../ic_star.imageset/ic_star@2x.png | Bin 0 -> 1433 bytes .../ic_star.imageset/ic_star@3x.png | Bin 0 -> 1610 bytes .../Base.lproj/LaunchScreen.storyboard | 25 + .../DesafioIOS/Base.lproj/Main.storyboard | 422 +++++++++++ .../DesafioIOS/Core/ReachabilityManager.swift | 60 ++ .../Core/Supporting Files/Bridging-Header.h | 15 + .../Core/Supporting Files/Info.plist | 53 ++ .../DesafioIOS.xcdatamodeld/.xccurrentversion | 8 + .../DesafioIOS.xcdatamodel/contents | 4 + .../DesafioIOSTests/DesafioIOSTests.swift | 36 + DesafioIOS/DesafioIOSTests/Info.plist | 22 + DesafioIOS/Podfile | 19 + 35 files changed, 1672 insertions(+) create mode 100644 .gitignore create mode 100644 DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj create mode 100644 DesafioIOS/DesafioIOS/AppDelegate.swift create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/avatar_noimage@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@3x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@3x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@3x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_menu.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_menu.imageset/ic_menu@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_menu.imageset/ic_menu@3x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@3x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/Contents.json create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@2x.png create mode 100644 DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@3x.png create mode 100644 DesafioIOS/DesafioIOS/Base.lproj/LaunchScreen.storyboard create mode 100644 DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard create mode 100644 DesafioIOS/DesafioIOS/Core/ReachabilityManager.swift create mode 100644 DesafioIOS/DesafioIOS/Core/Supporting Files/Bridging-Header.h create mode 100644 DesafioIOS/DesafioIOS/Core/Supporting Files/Info.plist create mode 100644 DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/.xccurrentversion create mode 100644 DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/DesafioIOS.xcdatamodel/contents create mode 100644 DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/Info.plist create mode 100755 DesafioIOS/Podfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f46e69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +#Xcode +.DS_Store +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +*.xcworkspace +!default.xcworkspace +xcuserdata +profile +*.moved-aside +DerivedData +.idea/ +# Pods - for those of you who use CocoaPods +Pods +*.lock +# Fastlane +*.ipa +*.zip diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ab0c4d5 --- /dev/null +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -0,0 +1,679 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B7A1FA4AF6700B0E95D /* AppDelegate.swift */; }; + 4A427B801FA4AF6700B0E95D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A427B7E1FA4AF6700B0E95D /* Main.storyboard */; }; + 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B811FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld */; }; + 4A427B851FA4AF6700B0E95D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A427B841FA4AF6700B0E95D /* Assets.xcassets */; }; + 4A427B881FA4AF6700B0E95D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A427B861FA4AF6700B0E95D /* LaunchScreen.storyboard */; }; + 4A427B931FA4AF6700B0E95D /* DesafioIOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */; }; + 4A993A641FA4B12E00A23610 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4A993A621FA4B12E00A23610 /* Info.plist */; }; + 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */; }; + 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; + D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4A427B8F1FA4AF6700B0E95D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4A427B6F1FA4AF6700B0E95D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4A427B761FA4AF6700B0E95D; + remoteInfo = DesafioIOS; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 181FA2BEBF8895A2D9637B8D /* Pods-DesafioIOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOSTests/Pods-DesafioIOSTests.debug.xcconfig"; sourceTree = ""; }; + 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A427B771FA4AF6700B0E95D /* DesafioIOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DesafioIOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A427B7A1FA4AF6700B0E95D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 4A427B7F1FA4AF6700B0E95D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 4A427B821FA4AF6700B0E95D /* DesafioIOS.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DesafioIOS.xcdatamodel; sourceTree = ""; }; + 4A427B841FA4AF6700B0E95D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4A427B871FA4AF6700B0E95D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4A427B8E1FA4AF6700B0E95D /* DesafioIOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DesafioIOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesafioIOSTests.swift; sourceTree = ""; }; + 4A427B941FA4AF6700B0E95D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4A993A611FA4B12E00A23610 /* Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; + 4A993A621FA4B12E00A23610 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; + 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; + 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; + D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E892893D796E7ECB915AC464 /* Pods-DesafioIOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOSTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOSTests/Pods-DesafioIOSTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4A427B741FA4AF6700B0E95D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A427B8B1FA4AF6700B0E95D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4A427B6E1FA4AF6700B0E95D = { + isa = PBXGroup; + children = ( + 4A427B791FA4AF6700B0E95D /* DesafioIOS */, + 4A427B911FA4AF6700B0E95D /* DesafioIOSTests */, + 4A427B781FA4AF6700B0E95D /* Products */, + 7FB0D43E8B06B528EC37013F /* Pods */, + C14EA81FB48DB572A80FE5DF /* Frameworks */, + ); + sourceTree = ""; + }; + 4A427B781FA4AF6700B0E95D /* Products */ = { + isa = PBXGroup; + children = ( + 4A427B771FA4AF6700B0E95D /* DesafioIOS.app */, + 4A427B8E1FA4AF6700B0E95D /* DesafioIOSTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4A427B791FA4AF6700B0E95D /* DesafioIOS */ = { + isa = PBXGroup; + children = ( + 4A427B7A1FA4AF6700B0E95D /* AppDelegate.swift */, + 4A427B811FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld */, + 4A427B841FA4AF6700B0E95D /* Assets.xcassets */, + 4A993A5F1FA4B12E00A23610 /* Core */, + 4A993A631FA4B12E00A23610 /* Models */, + 4A993A661FA4B14A00A23610 /* Views */, + 4A993A5E1FA4B12E00A23610 /* Controllers */, + ); + path = DesafioIOS; + sourceTree = ""; + }; + 4A427B911FA4AF6700B0E95D /* DesafioIOSTests */ = { + isa = PBXGroup; + children = ( + 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */, + 4A427B941FA4AF6700B0E95D /* Info.plist */, + ); + path = DesafioIOSTests; + sourceTree = ""; + }; + 4A993A5E1FA4B12E00A23610 /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 4A993A5F1FA4B12E00A23610 /* Core */ = { + isa = PBXGroup; + children = ( + 4A993A671FA4B31300A23610 /* Managers */, + 4A993A601FA4B12E00A23610 /* Supporting Files */, + ); + path = Core; + sourceTree = ""; + }; + 4A993A601FA4B12E00A23610 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4A993A611FA4B12E00A23610 /* Bridging-Header.h */, + 4A993A621FA4B12E00A23610 /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + 4A993A631FA4B12E00A23610 /* Models */ = { + isa = PBXGroup; + children = ( + ); + path = Models; + sourceTree = ""; + }; + 4A993A661FA4B14A00A23610 /* Views */ = { + isa = PBXGroup; + children = ( + 4A427B7E1FA4AF6700B0E95D /* Main.storyboard */, + 4A427B861FA4AF6700B0E95D /* LaunchScreen.storyboard */, + ); + name = Views; + sourceTree = ""; + }; + 4A993A671FA4B31300A23610 /* Managers */ = { + isa = PBXGroup; + children = ( + 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */, + ); + name = Managers; + sourceTree = ""; + }; + 7FB0D43E8B06B528EC37013F /* Pods */ = { + isa = PBXGroup; + children = ( + 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */, + 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */, + 181FA2BEBF8895A2D9637B8D /* Pods-DesafioIOSTests.debug.xcconfig */, + E892893D796E7ECB915AC464 /* Pods-DesafioIOSTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + C14EA81FB48DB572A80FE5DF /* Frameworks */ = { + isa = PBXGroup; + children = ( + D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */, + 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4A427B761FA4AF6700B0E95D /* DesafioIOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4A427B971FA4AF6700B0E95D /* Build configuration list for PBXNativeTarget "DesafioIOS" */; + buildPhases = ( + 9A9049DD7D95E01718403CB4 /* [CP] Check Pods Manifest.lock */, + 4A427B731FA4AF6700B0E95D /* Sources */, + 4A427B741FA4AF6700B0E95D /* Frameworks */, + 4A427B751FA4AF6700B0E95D /* Resources */, + 7131FFAB33D5DE554FDC1C82 /* [CP] Embed Pods Frameworks */, + C5BFD387178789BD351200E7 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DesafioIOS; + productName = DesafioIOS; + productReference = 4A427B771FA4AF6700B0E95D /* DesafioIOS.app */; + productType = "com.apple.product-type.application"; + }; + 4A427B8D1FA4AF6700B0E95D /* DesafioIOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4A427B9A1FA4AF6700B0E95D /* Build configuration list for PBXNativeTarget "DesafioIOSTests" */; + buildPhases = ( + 913D7D1A2182E387CCF591A1 /* [CP] Check Pods Manifest.lock */, + 4A427B8A1FA4AF6700B0E95D /* Sources */, + 4A427B8B1FA4AF6700B0E95D /* Frameworks */, + 4A427B8C1FA4AF6700B0E95D /* Resources */, + E9278D7BCEF7E5EF4B6EFA35 /* [CP] Embed Pods Frameworks */, + F609788CEA126B9E27CE8F8B /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4A427B901FA4AF6700B0E95D /* PBXTargetDependency */, + ); + name = DesafioIOSTests; + productName = DesafioIOSTests; + productReference = 4A427B8E1FA4AF6700B0E95D /* DesafioIOSTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4A427B6F1FA4AF6700B0E95D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = Nexaas; + TargetAttributes = { + 4A427B761FA4AF6700B0E95D = { + CreatedOnToolsVersion = 9.0.1; + ProvisioningStyle = Automatic; + }; + 4A427B8D1FA4AF6700B0E95D = { + CreatedOnToolsVersion = 9.0.1; + ProvisioningStyle = Automatic; + TestTargetID = 4A427B761FA4AF6700B0E95D; + }; + }; + }; + buildConfigurationList = 4A427B721FA4AF6700B0E95D /* Build configuration list for PBXProject "DesafioIOS" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4A427B6E1FA4AF6700B0E95D; + productRefGroup = 4A427B781FA4AF6700B0E95D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4A427B761FA4AF6700B0E95D /* DesafioIOS */, + 4A427B8D1FA4AF6700B0E95D /* DesafioIOSTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4A427B751FA4AF6700B0E95D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A993A641FA4B12E00A23610 /* Info.plist in Resources */, + 4A427B881FA4AF6700B0E95D /* LaunchScreen.storyboard in Resources */, + 4A427B851FA4AF6700B0E95D /* Assets.xcassets in Resources */, + 4A427B801FA4AF6700B0E95D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A427B8C1FA4AF6700B0E95D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 7131FFAB33D5DE554FDC1C82 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/CoolDesignables/CoolDesignables.framework", + "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/ReachabilitySwift.framework", + "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CoolDesignables.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReachabilitySwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 913D7D1A2182E387CCF591A1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DesafioIOSTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9A9049DD7D95E01718403CB4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DesafioIOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C5BFD387178789BD351200E7 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E9278D7BCEF7E5EF4B6EFA35 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOSTests/Pods-DesafioIOSTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/CoolDesignables/CoolDesignables.framework", + "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/ReachabilitySwift.framework", + "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CoolDesignables.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReachabilitySwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOSTests/Pods-DesafioIOSTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F609788CEA126B9E27CE8F8B /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DesafioIOSTests/Pods-DesafioIOSTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4A427B731FA4AF6700B0E95D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, + 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */, + 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4A427B8A1FA4AF6700B0E95D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A427B931FA4AF6700B0E95D /* DesafioIOSTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4A427B901FA4AF6700B0E95D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4A427B761FA4AF6700B0E95D /* DesafioIOS */; + targetProxy = 4A427B8F1FA4AF6700B0E95D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 4A427B7E1FA4AF6700B0E95D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4A427B7F1FA4AF6700B0E95D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 4A427B861FA4AF6700B0E95D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4A427B871FA4AF6700B0E95D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4A427B951FA4AF6700B0E95D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4A427B961FA4AF6700B0E95D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4A427B981FA4AF6700B0E95D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "$(SRCROOT)/DesafioIOS/Core/Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nexaas.challenge.DesafioIOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "DesafioIOS/Core/Supporting Files/Bridging-Header.h"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 4A427B991FA4AF6700B0E95D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "$(SRCROOT)/DesafioIOS/Core/Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nexaas.challenge.DesafioIOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "DesafioIOS/Core/Supporting Files/Bridging-Header.h"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 4A427B9B1FA4AF6700B0E95D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 181FA2BEBF8895A2D9637B8D /* Pods-DesafioIOSTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = DesafioIOSTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nexaas.challenge.DesafioIOSTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DesafioIOS.app/DesafioIOS"; + }; + name = Debug; + }; + 4A427B9C1FA4AF6700B0E95D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E892893D796E7ECB915AC464 /* Pods-DesafioIOSTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = DesafioIOSTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nexaas.challenge.DesafioIOSTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DesafioIOS.app/DesafioIOS"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4A427B721FA4AF6700B0E95D /* Build configuration list for PBXProject "DesafioIOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A427B951FA4AF6700B0E95D /* Debug */, + 4A427B961FA4AF6700B0E95D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4A427B971FA4AF6700B0E95D /* Build configuration list for PBXNativeTarget "DesafioIOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A427B981FA4AF6700B0E95D /* Debug */, + 4A427B991FA4AF6700B0E95D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4A427B9A1FA4AF6700B0E95D /* Build configuration list for PBXNativeTarget "DesafioIOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4A427B9B1FA4AF6700B0E95D /* Debug */, + 4A427B9C1FA4AF6700B0E95D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 4A427B811FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 4A427B821FA4AF6700B0E95D /* DesafioIOS.xcdatamodel */, + ); + currentVersion = 4A427B821FA4AF6700B0E95D /* DesafioIOS.xcdatamodel */; + path = DesafioIOS.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = 4A427B6F1FA4AF6700B0E95D /* Project object */; +} diff --git a/DesafioIOS/DesafioIOS/AppDelegate.swift b/DesafioIOS/DesafioIOS/AppDelegate.swift new file mode 100644 index 0000000..aee7b8c --- /dev/null +++ b/DesafioIOS/DesafioIOS/AppDelegate.swift @@ -0,0 +1,93 @@ +// +// AppDelegate.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit +import CoreData + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + // Saves changes in the application's managed object context before the application terminates. + self.saveContext() + } + + // MARK: - Core Data stack + + lazy var persistentContainer: NSPersistentContainer = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "DesafioIOS") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container + }() + + // MARK: - Core Data Saving support + + func saveContext () { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } + } + +} + diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..19882d5 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/Contents.json new file mode 100644 index 0000000..b8321a2 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "avatar_noimage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/avatar_noimage@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/avatar_noimage.imageset/avatar_noimage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..34fe8eed95bd3e7e4cd9958a37c89e048d02384c GIT binary patch literal 5176 zcmaJ_cQjnv+tx0k6TOZZEx{=zWeR9&e?mv>v^8H?022D*NMMrqQ^+bOGidV#%Q1qGdrKn z&mT)#iu1RcfcpIT#DmvC;LS0v_y9*NicHf5cGvWRv^H`MIC6RXe?{&tBrE^ z&?jI~<^&VCGr`Ll;sVsT3Q!A#o(rH+ct=1W+S>;Q4O9pIsS7<{|2E420e?d9Uh2UA z1cflZ3DCx1Q2<4-lC-m&oE$(I0+v%$xgxJ91(27OQ0|4{ma++P95lu$NNHMWC8*LzyVjl7_6I&90UT9k(HN`mzO?ANaKQh@Q#7f zJ~+_d3NRGT8SCMT_rUl7ek(dUVf^sw!1GA|lLFfJA6XyVUu8Nkm`tFfuZ$d6_IFBu z1C5RUe<&LL4;qIzL;W}2|5F$T5AsFHn4xeOKdkfl!MTEdyYhuJ>Tc>i6+ zo9-Aq2Ir3P1!!ykSv7#MqqB$4Z`+?7V`Hd+4-W6>9I&yM4N)TO`qPCotu9l{fjy6nL?r$y(yVv@MqdkkAIH^@~?V-ab5mB7R`U<%A5xy^ZRiB*J1x{I`5$0%YRz;eDhE9 zqkPUg9(&%}N2kwI$;eoq8Nf8*fuH-3*1r6mK~q_8HiIKNG{HWmO8Sr4Ko1Iy8jTu6 zva3J|;7nvjkBRp};Qh7C`ko+j3&EX|VRK;Ib3@T|Y)6${3tQwvowOR5X_b^2gG@NR z)HSl;>HUqQ);wzJ-VUc%s#mTT#M^Ah>4lw~o$U*KRc9F7B?>BRS247!b>UPoyv3~a zXB(ReowsXk${VMagYPtaqDh*8u%!@N zs^IGixm6*=P}Js^q+8UxutRv`TFnD0yn8GP#Jp!HPwAZdHs-68siRj=Lsg!D=|;R5 z+KRpSIFR8)Fz!>#W{f#iFDrG*QkTYEfnP^QN9C5QyrDerG9!`Aq7Qif1#nqP`LHAHH-Ce<7StV?BFLy?3d7#`u;m(cv@(dNq z!rH)c*%xn@gt+;Hcp29oKhEJ668K3_2i%##G|F!<(QWM?9UUJ1AiPod77&4KCLayf zaewJ%$>Mr_nmPtns-2jadFmVFx>$Zw!uzH5y8i>xlM(mP<^&G~TH;tV;Ci|X^wxlD zsTgJFy|C>eXBy!tu_~6@3w_FJY7D4?=;4d=KyL|u3HHeQb>>$h`U*zcj(7808+el& z)I$il8D38|`s+{-bkZ7(!?e=1L%Z$G!zu5=ESMBIP^8= z3u7s_-o4YhnvaqG{xeE=U0;N7*czsU`({_y;h@a3>X))PT;XtuHzQf3>T<@xhg+cw zS*3Z=ju`42m7mAQX}-0eNnXOy@e#P0;)K!94)qi}XRv+pkeuvn%1s}!n2NN-mzn1yBV()N z1Ywp~A3FCTow*{zkR$nVjB|#N_n4>O1I_%Tiss-IC->X&PdxoB0?FH$sG|fT-RdlM zm>kU`O3ha42J92cV+H(i#(6b-H0(2W_GjX67j`#85KE%d#WqXnpg6Zkk zykXc`vbjUFGNzw10g~O1SW-zZWb}8b3ZBi@b6L$d6?(Jf#}{c1t=v35A7(W=qie#86T_<;d;H zVY5ZLfBo)IK5*MGJK%~h$1MrY9!3LuKB8p0A&cs>isA`UzbNZnUUBafp4UIaWK*@s zO^L7dEQ|?tHnokA;7Ov@bNFS>#M;pyf7}-e59geT4Ro!^tYU`S=;f9;s&PoLSuPE~ zi*W#L+O&XUO1JhNb{VybYx&_7;j?`B_nl}r&A>l~dB<7CwU{^>Hb zW7dGQxYBIjzDpDs16;RJ^o+>SiYag%=DA>L7??JP?XtJ5nnY%nD3Ps`7SW>b}f z`qJ{>3C|Zj60t!{c~Ef0BuqRt%~Y-tcoCZqvCpvmd`YaO`?IiV7soJ1$H1@)B|q&v zY^E<|m9V;8YwJ88^oZ~Z@g)_qAWnt;A0h8I<_=;hjHTT+MCOW*OsH}cwe?q}WqxQ+s}>qnauXf6(IFvdpRZzp4kFwiw| zitrT0V?-bHuGTyj)Twm1Ro$L0Ic{kQigfhveIs&_G7}BxbOU65u|u*_L0VZFLry$J zGo6A{3lrjrBnQOf)}zyf{n>aCtq&FNqytwyixckLH?c{3AjfduA~%($_x#i4m46&2 zZ>6OAB>^r_$EjLb4drT&KREc~u3h9O_Uk&_&28&yx7MYOJc_<^cf~Z(XSoJbh&K6B-GM50FZ~Cn^tEG$EQ8 zGt%AYFxsiFwUCq#{Q6{k<)^2}ih6(tMJd9VP)2H#e-RxIdrvWHM2q#@#x^?3av#P~ z_xbZxd>Xk)%cZBa>^~7la{|qMUB;G*&@8y`x@|&Xp>RR7a{PwIaccjQ!Quzs^KPq} z`pr*f)eoW=*QRu=xbT~RPa^#lGX1Gws>f_bMoA%ixFbbfdZ*s0=(h0kf%(Li2)j5z zjp3zT!?J^q)`yCfVj7I^YrQR3yWR4pn!+#@NXKYaQ=37O&6@!tsptlO=-Nk0Xs(It zlwpls%JoTFyH&sZ@!*}6IZ#L*UcMkut;jVCF4cHe5I?7v{}Lg!lh|yw{Hl1s+=6%O zsIx?H*w(uzBR^)je(@)>(dOh59?IUJQpWihm`mx*o%~^NBkQH=+JRY?g-?rSx$DUG z-9ZllK2rHdiE5t*99$I=Yz_ssb45ib{#UGeB)f4>;~qyu2(x6H`M>v!E^)xGV4Ik( z?me1Zk_q6kdwof`1Q+vlu84+NP&T_?-Xd3OAd2o|Y(UQo5e}jawA_F5hub0YwUbi$ z-FHz5#Dmd^n;JC+pkCQ-IN6rguveMfis~PJQ%k!7>i&JIvT%u>8h_Y|w?giu`u0|u zcnjI#%zTWX!kQj!jhOE*-QuLU1iP}2YtzbaCN+>?%NV*+1|j=0Rp}B?ei+`Gi zWGB1@6G>796tAqg>nTzn8;O1IrNt&i1cewh3?HNqIbX^Z6F~J**Bac&q>wb*3HPTw zwt17gxS9%Su|O`uM)^Cto{H{trfLKcnNGBcluc7zh%KR1?X?7PM2FLJ_y7?;wy};x zBp{yo7GJKGBddZ7Jc`JnmbrWz%t7?2wb>@Vn2@~dAo<6ZU7cLv%MFUs`NSAlgIPxY zO$sj9WIaO;2R$4g_6XS3kzgey*z1yZ%qKGH5@T-k5dqHA?^)yEXM~~aCLKS{(pEVo zQ@j*lqDi?)rN&TtQ{r^xK{g3HwWx3%v1)9%%7g=EBat@5o@eZ47IAF_qsfbXN^E3b z)kTCLZ>-%tJoXm*u~)^DuPzsME({H)OA3jxUFgpGV=#>(Z)8P*e(DtughSP#mrGUP zy^Fl!?q0_NY&ce5_d9kI+DZCaK{^l*I@6%B#>{; z@`h?RIeaxyT=N6>xRFxu;uqUi^&|fRgnVi@nf2=^WWVzjnSp`R@vze$QOp`d%QE6_F*hdOcG8>wAszF!5)&T9EX2dJO?ZZ*(T&^xd(IH;M0Ov*N_{ zTJdXJ%ld8W{?^i#!Y4C)sB6v~&?tEo6%{qLBo$Qfd|}Xwii%h*VQ8fZ68@^bz8-&m z&Fm-+E;hTMHS`_t!W>dev1^5{xhZH=gTKkcjvd|av7hBK|m^)*!vj>l#zppEEFbH9FLpkBIW2a5RU zl;!@~(@o)a@M?&I!j{z2fwQ{lqXFDtsDKBeNut`AA$)vLt$9iBY^!K+eLC}QOfBzE z6MHe*iLQH|Io~&1_P=uFor(F)pm%cC=Xyep_D<*Sg&i-0Xym&ewQv}O9`0C~F3NC8 zDz=?ww}yUPz{(TR!%Zo`?}K)k%hxBRcK9;LM{qjbn0ez6;iIHY7XlJD8w@ zTT?10o255))!YJtzI8)3%xwoMgsYv6)T*ii2%D0X&DbCGdNY$20vi5>O{LN^ikZD9 z(^*;L_QC4|$e|ToxW1B?`pnodEvUxEPb$Zxv~c=#PuV{7V4=I)z%wd$yMMtmgA?B>ls_o0+$9k z3-+872uOi_hy}9YP81;oCkhk{{fjSrk^|RyM>5y->b92sk z`xkYCoTMb7WU3&y!ISx9<%;Go(#l>#NXV~Z!z$kNH4=G3+zTr0$KhU)-962R4G#ue ze$3YGncT>hc7-sT-@h6pHDxi8+loESr7t~O7tfq-eW7TR3ub;7cx-=BC48C|R~O&i z@sc}pEcFHbbA9!y#{0cbsRP!p-k!}e7jJFBY}O#;gNFuLJmZC81#i69Xd(D5uGzL8 ztz$L@rk;;mJRvLXEEYxqs?aX$7eM=+xqXKt@KGs{NXI<6;h3j1E8|kNhs16{`@MXq z-}mkIIF^KLBi-UW!A=PaS4%}TT~9^+mZ05FLK=t5eJ+r22iK~)dGg_s x7Nnq*#M-X$?AP^X(_YofRqWMedv4^^WZY6cE7FFaSbqP@Gte=CRcJXx{tqPja~J>s literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/Contents.json new file mode 100644 index 0000000..071d9a9 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_back@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_back@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5c6ec54fba1c7892952e30ba84a29b89c8ba6761 GIT binary patch literal 1317 zcmbVMZ%o`|7%u~1AZ)vAAKWBc?LKfU^xC&v8Q0vwxb}b}9>d!OPqPer?F(0+?X4}l z9v>zcGltG1i`%@JNEZJkn)rugE@mYDqhi#=XhLGLZ(cMr(`1rNw+L>&cW~$jvKX7R z@B2RQ^LzgMp6BUwUvF1k&7K+z!|KA_LKLkGdiQOuMBg{+W}iUIZYvbGj_89{S~MWm zA?pcJbY%9$QTzG=}-x?UX2uKnqX6Aw>%i zH|IYla77LfkFyavlHy@f=^i)Wk@4P`G(IBvWTL$d_uCvYph8Q;ZFN*LIXgfU?Q&>e z5K{zRRIx?^#9C1CNFUDY2E8r@Pj@NU|X9Rwh3owz*4l20yIej4$vG!l9cX1UrKrEg@bw2uj$n zW?G^q!IEzU`CiA;_r-F&0YytUV!A$B>Vm$cZs}%HPvJaYtUB(Fh?1fegpGwlt>rC1 zLpcHEPD5AmBGa6*NfiH$fi=ARe;K4uP*kB^{?$Gu5$egpc4HXOVPl-2hGxh>!}5F8 z$}S8mUSJEs^Z+qI**j&bK5& o*z!-`%?)tZT`y-B%PStjgqQZN%%7^=R`_v+L%qVg!Ka@28%ngo3IG5A literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_back.imageset/ic_back@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bc400d655db48d4cd5f85d8c4c9e9a9966f73df4 GIT binary patch literal 1670 zcmeAS@N?(olHy`uVBq!ia0vp^-axF+!3HE(Ynv|vQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07??FOLn2Bde0{8v^K*7iAWdWaj57fJ{tG z$}cUkRRX#c;)UD-xUqS~&|m@vn0`fKfxe-h0mw@*g}%P{mFDKcRTq~8r6Sym)!^cg z%7Rq=pw#00(xPNw#HA^NtSYc_E=o--$uA1Y&(DE{Vn9ZINq%ugeu09sGdR=~G<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tjrBuO-v1)%$=Q$T@4Lg%`DwqOq~s# z+?*WET%BFa4Pkm+@{>z*Q}aq-dQ%X3EpX}uC5YStpv^9+MVV!(DQ-pixe8#9TV-N# zi>Zr?qlvSn5l-`TVfu0()Q=MTd&t` z{+_umLMMoAv6u|g`GYG1;;qb@7cFaSPLO@UKL5a0f%urIhJnEc121UIU*2Gxa5W>9 zx7YJQOGrrGj02k&NZBy#ow}a1G&!w2_mUAQ!r{BT6vY#>4fRM3MEJ+fUvXo!z5o8o03fn&5`HZFzV8`ip#D zVXkSnp>0wr&zmn2{5uxiTegcgC^PbFg5`}hPwq1A4i*!25;Oml>cyA8eh#y~=EoH! z&Ci=#yuJK-ZG_MH#fGh5?iReb*{PRr_kqHIcjt7NwmaP0rPF8o#Ozz$)RU1MyAJrg z-V_ley)#+Y_lB>*=}kI)j2rx={^z^i=)KZ%v|_?Pwr`WI6V>{;PDf8W`tfM+4W^YF ztWIz8IuvEw8p~R&b6QLH8F~% zkz@5{oz*5`Z$1cw>7Sond3uw@zR9;0Y~E|;w53MM*q@Nyk#%UA$L0BXkzyxH?myA& zIo=g@dXq<)#*RaJ8kOEB_uh(4jtpCQXx3Bl0O7LfU$(QjZ_(+S|D^By7TN2S33aOa zD%$T4@?W^0yv6O;cZ;{1&t%?RyeXzeb-U@=y*Yms5^JCEx$Ro>ea*v*>Y^o)cHi5D trfxkYwdcvLCvKnXTVwgZ9<67QU=W!xlPBXx<4jP^;OXk;vd$@?2>=6ghlT(E literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/Contents.json new file mode 100644 index 0000000..ca59192 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_close@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaea4466c21b1e527e80c1cadfdc1b2241ffdc4 GIT binary patch literal 1566 zcmeAS@N?(olHy`uVBq!ia0vp^MnEjj!3HFk^3IC{Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07??FOLn2Bde0{8v^K*7iAWdWaj57fJ{tG z$}cUkRRX#c;)UD-xUqS~&|m@vn0`fKfxe-h0mw@*g}%P{mFDKcRTq~8r6Sym)!^cg z%7Rq=pw#00(xPNw#HA^NtSYc_E=o--$uA1Y&(DE{Vn9ZINq%ugeu09sGdR=~G<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tXxe@jod5@44j?aTnr6e%`6<9Oq`7j zESxMY3=GZ84Pbg*@{>z*Q}aq-dQ%X34RPuPC5YStpv^9+MVV!(DQ-pixe8#9TV>*M zi@BRCPV=C8Q*gP(+|3QAUVWfr^g+>!6x}c(U>X83;fWW>fhYgeJYbqH0w(T@JG+)L zFfjFax;TbZ+&VMkwD(ml^Tx@xS{_}QZOG&zx5nCWm$!bPWYM-53$LEWe=MqW!= zMZQFAd124t)xyDT$YH{)oT0elN5l$6jihOfH%;?r|M)p~M(eS;#_7+>ukh8FmCHS^ zd7uC6%*;+UM}fp!ju-gOH3)uSS>yI(E$f!X)DIkIHo9pl@)$YPZ@8T1z*lTDJ^lZW z2*%zy8)Mkbo1d7m^>K7oWPSKJ&)BZ@=g!Z(`@#SX(B;nbZ?lzZrh71(OZ+|8T;zE? zu)TZg*`R|AD-yrOvzI0~9ol(C+>F!(!YgHux)mkZyKxm#`~HTi<^$py8PGiEc*m&%}ajW3s&6uV6y9xttyXC zm89lu+OclUcbAT;K*{YLJG^@OUs`vcG;{o#$E}&5v;1^VrPr6-ODUZ1j3@L@@G-~~ zy!gr>e&N37!AY-{v_5(LUG-Fy&9s+>299S|u45NhX)Q4bT9g0i1T*)MJMt>oYjqaA zReHf$IHi_#Yr>ri^Asx;!kUh%{12O)z*;R)^G)}Atc>jSba$uyKj+BB(k@Vm?dj_0vd$@?2>{vWM3evk literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_close.imageset/ic_close@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6df3eb22041403343390e08f215bc898f28d4e7 GIT binary patch literal 1677 zcmaJ?eM}Q~7_Q(ZvH{A742*al>QJF~EtGyhQQB*vLQzVYMdILS54h3xy7o#7IHnd6 z@e2@9WIBh7pTJmAoShbl8L5=g<)a4jEv(bxn6xQY+1 zV@YADPKc%Bk=c4IE;~l9%+649Rp81nAk@sG2s9Xp0%na`Yv7sr;5aXjvh8jN1ja+i z3_kd-D1~$_ASCn{z@i5SC>aa}z~<5!EKVSkTw;36I#H|h^7!mk`GdqzH33Fo1)bkCdNb!3^JoSh(U+#ElmbWrT-tQ(M&}fNE!CN z-v1;v$g^}9B*P4ZQLm&Hm+EUzrQ-?p7)la)IYFo=r?@tqAPGY{p#y}%@u>lRQdEg+ z?apQ6D5;bu(HclptHdM-AEbEbIIiM}f>=y3TPzd`#R!8T3g(0hf`m+%Bj9piMlc(m z#3F>!sKK=4Bv$nvi%g5P3qqr#G9#EC&%{(>J)r@{8|UHE*AhN0-vm}QeJz4%u@F@Z zWMA*U);;N>G-NlY#7kYKWB(YvAq^QbCAMdT4%G zBZ6wMSiVjXRkmb_b5uFP;v@Uc)VqeX=51wNo)_EJdOKGi<}UM9=lfl+$&KF~re9N! z_ez>#JYHL(zD;l<}o&6Pt=^2;jQGxjRSs+Rt| z(yHc*(Mh{>4-V%P=Pqb;EU~6I)#3fWCyOeF?pirpt1StGt4}@jwr=c$jj;!<>fWBh zt1m8X<_-!PYzM9G>w-ff-b!a*xRc)hbLENUoW7yP8|z)#y%VJS10T$B@@ zRrE;ESKjT#O=xT0aBfY0U|LM9+o7tmh(|RoikYoq(Y&)iXbpXFh4H?u1gTr>z5?!; zT7j`bZ~2r)JW-3TEGy)dZ}ae!?TRQ7mj2dhDJ+KSuzS|)%?^IgIvw}SZEr6z&e^!A zurPMnv6_+&mMXw?v)^|SFAufMxc^CBLwDyjt|2ktPmc?K7m3%8WY|^@l_PzxN=LR_ zX%DixMmYB$UA{E;a|`b&gjeUYM}wZ^JV85F4ZOxX63@XWpV!^8q$Rs-bQre9{^54W ppMC$FC^Knz?)@3F1Op>+n`Y3GF9zXB3W#n0K}bX~NR1$Q%Ri3yjJp5; literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/Contents.json new file mode 100644 index 0000000..2c49515 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_fork@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_fork@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..147498ff929a26045f851eb5fabdc231d87b4181 GIT binary patch literal 1532 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk&``nLQqR!T z!q~`EN5ROz&{W^RQs2-(*TB%q(7?*bKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$IMft0d=ry1 z^FV@{U|qhxR-SpqC5d^-sh%#jN= z1y1vzdQ)(_#S*7peV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iCT_b27Y;BmFlBkV zIEGZ*+A=-1gD+6xc>4B@iOyG!3GKBzB|42;b>~GfueBOscQoTw#dEG*pZ0!}NA=>E z1t;9sE(!2jedW|I54|0cuU{YP+LIc*>0Z^}bKmSseT=Q&_sm>gH+N?GxjFwW&+j>J zc|JN+Z@TUEU$?X0JY{;(a5SK$xPaqdh31-lsUPN7+uulhvtr>g!FNqFUH2CruA9Mn zHjXV!_E9wRag|$alN0&A9I}eAJX3KwQG8B=sAsEhhWH_N-vc+QEf&09Dww>s$Ij;NsEX58^qJJYv!KYtjm>t;nkII7W?dKm4tgcr?%XhadKmG zbe_#r`8ut`WF6| zWtqp78@CKDT0Atl_By7%eYxkQm-S|gcb=;?RQhTp9{23Yu8rj%jPnye`|7v7VUH6# z^i<%!O0Le`#+X_;^*8DQ+bm<(Z;4#@p^xXwZ{FDpr%c^&bNB5;>z=+B!B_tMG0NMZ za;R77RNf)Jx%cCy9ZLKbcKm*2#oQ-HO%BCBn4~pj-j<~SK@a&tC6?NF$!9NZIN*F^ zW^2Zrg5C|^EKmFHt&;d3_}pVls9W*8L#$J+eXWj!9~QJV-hXdqCFlC^&gAo#qgUzl z9hR^?@4Cb9=seAQd&_^}S?W(z@2q9s^We%Q<2h2hwYd}RlU^G?eIffd{MIso>xX!M zZWfpOVS2@;!L>kaTE=I4=Jv~6SJ~eE=Q3b8u%KQfihr3ts66&`^>bP0l+XkKB;ro) literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_fork.imageset/ic_fork@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a0860fc983adbc003cc59c9ebd14526c87f4732a GIT binary patch literal 1792 zcmaJ?c~BEq91fyEP)G3q4=iB?PsnB?2_`0D$c97NfCz#WHDR-a7?X|30wEqFgc>eI zDo{aF6-CFZfG8?B3R=%9l_CYjiy)&`Q9R3F&~8w)f0XXb?(e-f-}n8_H=7wQ4jSP! z(TPH#j0g#aBS`CGdxuiVFL$D)m^2dzQ4Fydk0+9)T8tu);c*x(L?xAD5tvk#vgRtr zqfqRpDS{x=#wlEQEn* zgjj+i*r3H08^n>QL5Xr?fd4!iPtPR`qsLn3^1DQZHh_fhMn^ee!a4%I3& zgaTL7Y>Lu2Jdxl7WTc-{P-(u(s&!w=L>3IxOEn-wXV_Be2O`M-LshD;XdMxOeT(-$ zg>{iB8VroUba2dlfomgie06^n!xL}<*CpT@no!tRHQIDUiYnB$ z!x?=n1mT9Lb%a!nVj(aeAa&>pg^Vj?LL31kVhe?{MG$E@fiO!51q22N*dih92laDd z98FYVYNDSj`^NPfkZT(Vm4@^TV_HQLCJWT!Dq3IST*bh#_ztM|g)1947S@1VkPHU2 zwfA4`?mr@D$hQ1CyyWKV_+x5v=C$N-*Ja=8Chv?o1QtZ5(#yACtjXoV&-L?Oa9WpkwLM?jdi&FD`HkzviKEZAmG7+tn|dF0@XDz<4v+M9 zTa5P}=Atds!jCt5dJJwTI6bN-qRz8?==DMF=8Z>kO>1Y4t5ZF6?!M9NY27(yw`dIC z<%nzZIEkm}%J8mDALME0$M53jT;4vMS~S)i)ONAcC^2<489OY7)XVby#$mfB$J(D1 zIJ8hlBN<}X&TRUI(H|Q-R+y0?xplFn{*Fi5M{a!-O_&mLlV0VH!XK;hE2ze8mLiXG z(}%NbR=TvjjDCwW>~EZy$D@!zZfl73~({$bG11%T0{QOUUxalnxe901* zmCxQfFr0ZeyPqCDW0CHZTmACT=o3#%dT*5_nA$t{#FwyB-1dr(D{JShSq`wv#hRw{ zCoxs)%;rJKr;stP=YcyK3)S;xC7~ zpR`UaEt^(5VYC0f)$}zFI*7mn?>Len+ub`>T~e*06E-OYzo{ z8olEz%RTQx`&BIWik-VfjaRKM!B6UrMYlGZddwM)kkkI~@h^fjylExHo|8N5l{+%+ z+P#WjXS{jn+sw}SvnMp`V?8i3b(_l(<_bm?B`^Ngt2#-VVzV?*TKd83Hp z@!3yS($|-vSB*v&K1H_5iLsQOC2k8q_FoDssJ~s6dH=C>7VoTmO>N+Tn!;&s+GNY% zF#BPrd%u$zniAz<2<2seIPj1gq`i5&>z`4Qu?f~Bx z%HFWrwsr3s*)*%B z+a2!a-YAVGw5y6_#bmK+wnfch+RzJ-KgF%|%XpZw@gx4N2knDI3yDG0B0$Z^(6KT*OT}8PU4x$i+BmJF% zZWd$>J69%LFxrt#n)NYmN_n6p{U5691+tU3fo+Mlgc~D*7+~{U5h{Z^Fi@VW{^2$h<+=l*o7l4mEbTZ@qZZW3 zY~s9DJQlX7UXH64kA*ABrExH{+upz0oj2hba*u`K#h1eP0|U>zg@?O3?!SN^#$-$k zC!Lp{FK$_LucJcz9*|1M!d>#%`30h@Yhs#ZdOmueZt#@7=y^V0eWzsP#gnbKx_{hU zS_&Lm_?hn8<#6%wcPC0_%IEG*`uEou%ilJAD4BX2sC`hqfjT%hdW)`Yy?Ugq;_;Q) z(N=QBx-)wwJ+C}$u$0BiYqNV55FTNOR1ONa4 literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_menu.imageset/ic_menu@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_menu.imageset/ic_menu@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e58c2ff0f97bd12eb42eb3f275e002986edea5ff GIT binary patch literal 1180 zcmeAS@N?(olHy`uVBq!ia0vp^-axF+!3HE(Ynv|vQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07??FOLn2Bde0{8v^K*7iAWdWaj57fJ{tG z$}cUkRRX#c;)UD-xUqS~&|m@vn0`fKfxe-h0mw@*g}%P{mFDKcRTq~8r6Sym)!^cg z%7Rq=pw#00(xPNw#HA^NtSYc_E=o--$uA1Y&(DE{Vn9ZINq%ugeu09sGdR=~G<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tc=_Y3|tJ%+?<__T@4Lg%`DwqOq~s# z+?*WET%BFa4Pkm+@{>z*Q}aq-dQ;$f%?)ts1to~w0-((qF3TTkQo8j)pn+8^BB7C0j0HqQB>b%{aoN1(;z{QJEB3oD z=QE#cC6lv}_4HAz*XLH{9;?`~@@k|k-@=kZs|q-lUNXzcjVqaUJ8~`8yyXuYmMe<< z(O~7)*>HfV8)hn}o4d)LpUXE>y$?LV*zD(Pe m=)bf5lKMU_?&tr+5*QReT1=5HzU>GqFFalST-G@yGywqZv6g87 literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/Contents.json new file mode 100644 index 0000000..edbda68 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_reload@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_reload@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b36551032616f23baa9b1ba2a6afd6a39839ce1c GIT binary patch literal 1706 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk&``nLQqR!T z!q~`EN5ROz&{W^RQs2-(*TB%q(7?*bKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$IMft0d=ry1 z^FV@{U|qhxR-SpqC5d^-sh%#jN= z6HfD>dQ)(_#TlnweV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iChkK8uG@imM#9s@ zF{I+wnVH@>K7kU)pU0Ul3SM(6WOCINZK+dD8!}oZWaN0}3e1iYjNP(vfzM(g!^u@S zIjU2`ILzd@b!I7qY52MX2qm{|nPhmYDy0Aa%$mt}VxG_0`TU-7^1dhAEx%ho|9!vs z{A26qQ9TqWIyhTIe%$d(xflVVn-SN@x5#MoxrhVWe!(JqecYlw#GDr;EQ3}8xFdL|4ffO z9~->qBM0xdqf*HgmKMx)nS9M#R&3nd#kF^VU-7fu;f=Q)S>6R%$nz=qT;Yt2Z7j~@ z6o~A0EUbK2A(&y!_C(OY+n~Yvz|;wm)_k*UDlgsEVVvA#{zBX7{M--5Y{E_Lj}@{a zVy4fJjN!E7keg?sv+nZoyvf2hR9^0lYJQYtxBc)Pm76LOv$i{2deHLXOXT#>3uRBX zRkO96WfVU!k0IAZTjOTQ?P6x8>;wJ+%=I-_wjW^e7rVF9rLWQLgV_Ry?oF>9(t>Bb z+oH4djbraji34RT&YWE)b1t)H_i7#W#>MM4am>AN%I(311$#EmVd3LCwf!Hv?w{3H zFMTqRzw&Y6vFisJ8$a_E`AzeoIK~1Dra^*CktQ4rWt?EHeWVW^sKb%(pcv%^x5Iw@!E`2Q$AcM z@eq2=Izvt9#Lbl+a<_T9te19dzV~wV<+peAnpqiJJ|4bV>iV1gZfOc{Y^aHmmuZvzY&135}Ke+IY9?XlajLzg)v~g{K}iN?VlIutgvE#>D9%vt{+Uqeka` z?x^z?eWAHsHZ=3Yr^;_l{jNI>)adR!a7AoJeAACS<3xuW;jgx8>2K6M8~tx;f_FVdQ&MBb@01Z!#WB>pF literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_reload.imageset/ic_reload@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c3747d16514944f0620db8d2fd26a2fbffb788c4 GIT binary patch literal 2126 zcmaJ?c~lek7DfpKgCMO5RU$Zz=tDzRBq1TFkd3H83@Er_mXVBECMF|9Yh_7M1VpP= zo)9Y4suWOzETUE+#~s|kHh?}-R&8nBsucA-JF#NlACEWZ%>0&fzwdtc-tU~*9v>(6 z!3E&l+}wO5u>u*o@?5W%C;H{SX*Z(FWJH*ZB*1Htbh#09i&nu35RmBPsh|v$t2VB` z2BHY}5GXMjNtQ0;Dq$T}?uwzBbp{lTlFc<6Q017Y_(npaVpKT-okSdCl6fUJp z4SY}y#cnc!37g^)m76q5j*2vQ4!|>WQ34%^$N{rXt2c4YQKWagTy*ccrjdYmA&4f5 z^p~h)X*|G(jUW(CWl@w21_NMosEqK4FlKltz@#(6X><;aL8s6eTn3%Xqyz6C5}M7Z zTFaFQMDKHM*njS`ljj1#Shr^-KnKUMof<{nG8}*3XOwpUBjW7s6 zlhOzo5D3--E=IWm-hf1rP^EvBpfh}+)tlb832hk7EH}^?RJyCAkwB^R|A*>yAEHf& z4E$T~|0FggZZv>28EAqx7?tSY)=qP!GI04uP>#UHL>ShNbTM8HBd|#g8vs84UDtpR zsay%^U6(W8p`=o-L~lamdL<|kM3GP)Dg>#x?C_Xyk%-OV^F=}iL&%C2#n44Ufshjs z#);rF!bY$HSh+z5>X8ww>ThiHs92XFbOtoD05n2rph{$fb-=sAxzOlZ*rW2j$Errx z5-}>4hKixN#`|C69=Sq2Q^uLrlz1wn`TFrZC@wtd4EcO#93!Ii!oBbjMHjE{25@9iZ;zVLS z_OsAjh2&21o`(N4-nXtU>@3}XuKhvFuzhFi{p*8o;Kow8`4r>KaSdzP0=i#t_RF$q z>M;RX5lj2AAs&zI2~Wo2GfM{H3Z1Y(VGj-^6nI_(eo#ctwF?bFsw_6{RlQH>omksg z**sRrkM}y+2CwlG4rM&5I+kb1DfBEt1_mP@lH0G;jd`))7*6N@@|WKnuhb#S&^Dadhq#KtmpjPx?po<&_i{asmO?+gE$ zTY)pcW;1n)H!c{v*By{VmlHis;?K?#a(+<)=ef{JyeqSy{|FpK~>zdK~D- z&*?enK$fg`%$?Qxbb30W(Ay^gupXXmg|js}g3qtATew>J`q*!pXQ6+>XHK68GtziV9~vw+&pmdR}zM`L>xic=fI}pY=1QsCaW%aDYeSz~EG2sk3NCS9)vJ z)OF%2r+7+Hke@CaHZ>RI&al_=w0XDEKdR@|&R*o%B8{Oy_xn<`DgB|=*9{E^rZ@8n zx0(`fzSUT}-)tglngUlh4{xz_9e%XOcW;X6WgQnf&;K>HcDwd4sLCmZC(4Gp_^1TnwH2Ne~jz?cb|nkFm6g+Z6Q!C8Ow%DYf7Cx zcRvMo_5=+r`)38xzN4f%aQ~4RpjzG*fG1KWoX9$4^?q@8bLs_sK;S^X@#1cZH=g)r z{A&m^$Gc&^Z+Txfk)0}mcx107UFCNq*^$HSd1CA(S^t9tSzDH5VkX;}%hLC@F+0ap z=eOsQ6=6F%ep(eQX13NH^@*9-UK`)Wyx!5X>NcbTkvhomtXMkpzZu2hvY;lA0uTVKx^+q&(OYiirG-B)TWa%x{k7SHY}X$cBk z!S1ecXf+2G#E=~wti?;K2t~eMb$@1$9=d^faN$u+#n8UXnI3DheLBxfzh3!Leg50$ zHulrRw8ERk?kR0~H4fjv?$T712G=D^phVp64)3Zo<5zTM`F1oWJ^lG)Umx55;J<9P mC0Cb5PiPWHa$kru$G9E)0Ut>}ojlX^hnEQB1jl36Wd8|-=V2)T literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/Contents.json b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/Contents.json new file mode 100644 index 0000000..d3a4cae --- /dev/null +++ b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_star@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_star@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@2x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..780e64bf34a57ddf1ef740e6b3b8c835f00e6a54 GIT binary patch literal 1433 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk&``nLQqR!T z!q~`EN5ROz&{W^RQs2-(*TB%q(7?*bKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$IMft0d=ry1 z^FV@{U|qhxR-SpqC5d^-sh%#jNEO zM>AJv7jr|HUYGpj(%jU%5}4i;xL#)ioO(eCBDVl&vrB4GW?5>ATTyVW@`h)FH zCkRe&SY-e7kweGtJy*W|h@QjzisxSCGW(-?@|7|FgpSYbdEh3Hy?n;A9F`aC7wv8x zZD7oMZQ%WiWlK(KvxWeFkb{xOgCeErlXdoWsTJ_%9kkuS93JDoKR)B5;zy$uOpaZ) z6VFH-RgYNgUYoc0ir0kKbB@F&STsCRys-8q*IdR4Wq;ZVXt;`!M=q?NmjR({B!pCdpYIE%}MO6weNH!3n%g}-WA|BCw)?h)R`GuPKt}V zH!(H;zvgI}!JfN-DMw<hBRzZNn^RdV0GbTz-pYhQ=Aess#ZjvcSgPclCf zZvXu8#&ZR;13xTVz5bgPAAgy&Y`I-!t?skCuP+`-sA6colipceA=7-~<(|r*j~Aa! yOprd)99f`YI4g7JoCWWVH$47%ahaAq6A!}$^Tf{Z+_!$9!qU^#&t;ucLK6VtEEfC# literal 0 HcmV?d00001 diff --git a/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@3x.png b/DesafioIOS/DesafioIOS/Assets.xcassets/ic_star.imageset/ic_star@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..984d4e8f69fa0de7eac89fe27915e8ece7112df6 GIT binary patch literal 1610 zcmeAS@N?(olHy`uVBq!ia0vp^x**KK1|+Sd9?b$$k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk&``nLQqR!T z!q~`EN5ROz&{W^RQs2-(*TB%q(7?*bKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$IMft0d=ry1 z^FV@{U|qhxR-SpqC5d^-sh%#jN= zB~J68dQ)(_#Sy1oeV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iChp>nI5`FerX8Lx zjv*DduFSmc9TF&VtpDzTu34KLjD?#P?RnC>X31&qM1hGHg8BD~baX9nOA1i56Z;~d zW*hQF{zj`)(~9;}%izetuJ`=(!4wlh> z#^-$eVtI7muAGl0b~9MCMc2(`ls=fV&i3vqjffo6jBN)m7am)+s+K9b=;_PQ1$UPx zY!te1==avDD5*OPX2Btx-1D!zPYPgBVwC$OEumhhwA1id@+Y@t2hQg#+WzRXPOHdr z&PhL_rybt((_Gm6^TE9C7aIHCa9)&pu=L26?g_dvZLTHeJEVjThCBFYeR-j17G1uo z?xc+Exdv-7F0t71yKQ@fAMA5&>Th<}dR)R>aVSwg{2Q}L(~4I;7Chgyrp#WJ)AQH; zuis%m(VUYPN4eR3+M4_@t3?Wn4Kff$ujRv*zPWUe`e2SN88x9iV + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard b/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard new file mode 100644 index 0000000..7651c0a --- /dev/null +++ b/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard @@ -0,0 +1,422 @@ + + + + + + + + + + + + + HelveticaNeue-Light + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DesafioIOS/DesafioIOS/Core/ReachabilityManager.swift b/DesafioIOS/DesafioIOS/Core/ReachabilityManager.swift new file mode 100644 index 0000000..b0da666 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/ReachabilityManager.swift @@ -0,0 +1,60 @@ +// +// ReachabilityManager.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation +import ReachabilitySwift + +class ReachabilityManager { + + static var isSubscribed : Bool = false + private static let reachability = Reachability() + + class func subscribe() { + + if let reachability = ReachabilityManager.reachability { + + // When reachable... + reachability.whenReachable = { reachability in + DispatchQueue.main.async { + if reachability.isReachableViaWiFi { + print("ReachabilitySwift -> WiFi") + //NotificationCenter.default.post(name: NotificationCenter.Name.ReachableViaWifi, object: nil) + } else { + print("ReachabilitySwift -> 3G") + NotificationCenter.default.post(name: NotificationCenter.Name.Reachable, object: nil) + //NotificationCenter.default.post(name: NotificationCenter.Name.ReachableVia3G, object: nil) + } + } + } + + // When not... + reachability.whenUnreachable = { reachability in + DispatchQueue.main.async { + print("ReachabilitySwift -> Not reachable") + NotificationCenter.default.post(name: NotificationCenter.Name.NotReachable, object: nil) + } + } + + // Fire + do { + try reachability.startNotifier() + ReachabilityManager.isSubscribed = true + } catch { + print("ReachabilitySwift -> Unable to start notifier") + } + } + } + + class func unsubscribe() { + if let reachability = ReachabilityManager.reachability { + reachability.stopNotifier() + ReachabilityManager.isSubscribed = false + } + } +} + diff --git a/DesafioIOS/DesafioIOS/Core/Supporting Files/Bridging-Header.h b/DesafioIOS/DesafioIOS/Core/Supporting Files/Bridging-Header.h new file mode 100644 index 0000000..26f5ed1 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/Supporting Files/Bridging-Header.h @@ -0,0 +1,15 @@ +// +// Bridging-Header.h +// DesafioIOS +// +// Created by Felipe Ricieri on 06/01/17. +// Copyright © 2017 Stationfy. All rights reserved. +// + +#ifndef Bridging_Header_h +#define Bridging_Header_h + +#import +#import + +#endif /* Bridging_Header_h */ diff --git a/DesafioIOS/DesafioIOS/Core/Supporting Files/Info.plist b/DesafioIOS/DesafioIOS/Core/Supporting Files/Info.plist new file mode 100644 index 0000000..2fc8ad6 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/Supporting Files/Info.plist @@ -0,0 +1,53 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/.xccurrentversion b/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..808b9ae --- /dev/null +++ b/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + DesafioIOS.xcdatamodel + + diff --git a/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/DesafioIOS.xcdatamodel/contents b/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/DesafioIOS.xcdatamodel/contents new file mode 100644 index 0000000..476e5b6 --- /dev/null +++ b/DesafioIOS/DesafioIOS/DesafioIOS.xcdatamodeld/DesafioIOS.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift b/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift new file mode 100644 index 0000000..120cdf6 --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift @@ -0,0 +1,36 @@ +// +// DesafioIOSTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class DesafioIOSTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/DesafioIOS/DesafioIOSTests/Info.plist b/DesafioIOS/DesafioIOSTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/DesafioIOS/Podfile b/DesafioIOS/Podfile new file mode 100755 index 0000000..7cb3ec8 --- /dev/null +++ b/DesafioIOS/Podfile @@ -0,0 +1,19 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '9.0' +use_frameworks! + +def shared_pods + pod 'SDWebImage', '~> 3.7.3' + pod 'SVProgressHUD' + pod 'Alamofire' + pod 'ReachabilitySwift', '~> 3' + pod 'CoolDesignables' +end + +target "DesafioIOS" do + shared_pods +end + +target "DesafioIOSTests" do + shared_pods +end From 3e8ace4885f801675fce041cd6ac5a12edff7d82 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 10:49:51 -0200 Subject: [PATCH 02/37] Swift, UIKit & Foundation extensions utils --- .../DesafioIOS.xcodeproj/project.pbxproj | 20 +++++++++ .../Core/Foundation+Extensions.swift | 21 +++++++++ .../DesafioIOS/Core/Swift+Extensions.swift | 18 ++++++++ .../DesafioIOS/Core/UIKit+Extensions.swift | 43 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 DesafioIOS/DesafioIOS/Core/Foundation+Extensions.swift create mode 100644 DesafioIOS/DesafioIOS/Core/Swift+Extensions.swift create mode 100644 DesafioIOS/DesafioIOS/Core/UIKit+Extensions.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index ab0c4d5..4b522cb 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 4A427B931FA4AF6700B0E95D /* DesafioIOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */; }; 4A993A641FA4B12E00A23610 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4A993A621FA4B12E00A23610 /* Info.plist */; }; 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */; }; + 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */; }; + 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */; }; + 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -44,6 +47,9 @@ 4A993A611FA4B12E00A23610 /* Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; 4A993A621FA4B12E00A23610 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = ""; }; + 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Swift+Extensions.swift"; sourceTree = ""; }; + 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Extensions.swift"; sourceTree = ""; }; + 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -124,6 +130,7 @@ isa = PBXGroup; children = ( 4A993A671FA4B31300A23610 /* Managers */, + 4A993A6A1FA4B3E200A23610 /* Utils */, 4A993A601FA4B12E00A23610 /* Supporting Files */, ); path = Core; @@ -162,6 +169,16 @@ name = Managers; sourceTree = ""; }; + 4A993A6A1FA4B3E200A23610 /* Utils */ = { + isa = PBXGroup; + children = ( + 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */, + 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */, + 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */, + ); + name = Utils; + sourceTree = ""; + }; 7FB0D43E8B06B528EC37013F /* Pods */ = { isa = PBXGroup; children = ( @@ -414,7 +431,10 @@ buildActionMask = 2147483647; files = ( 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, + 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, + 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */, 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */, + 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */, 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DesafioIOS/DesafioIOS/Core/Foundation+Extensions.swift b/DesafioIOS/DesafioIOS/Core/Foundation+Extensions.swift new file mode 100644 index 0000000..1dbb175 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/Foundation+Extensions.swift @@ -0,0 +1,21 @@ +// +// Foundation+Extensions.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +extension NotificationCenter { + + // Notification Names + struct Name { + static let Reachable = Notification.Name("nc_reachable") + //static let ReachableViaWifi = Notification.Name("nc_reachableViaWifi") + //static let ReachableVia3G = Notification.Name("nc_reachableViaWifi") + static let NotReachable = Notification.Name("nc_notReachanle") + } +} + diff --git a/DesafioIOS/DesafioIOS/Core/Swift+Extensions.swift b/DesafioIOS/DesafioIOS/Core/Swift+Extensions.swift new file mode 100644 index 0000000..0721550 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/Swift+Extensions.swift @@ -0,0 +1,18 @@ +// +// Swift+Extensions.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +extension Optional { + func unwrapOrElse(_ val:Wrapped) -> Wrapped { + if self != nil { + return self! + } else { + return val + } + } +} + diff --git a/DesafioIOS/DesafioIOS/Core/UIKit+Extensions.swift b/DesafioIOS/DesafioIOS/Core/UIKit+Extensions.swift new file mode 100644 index 0000000..dd27408 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/UIKit+Extensions.swift @@ -0,0 +1,43 @@ +// +// UIKit+Extensions.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit + +extension UIColor { + + // Custom Colors + static var navigationBarColor : UIColor { + return UIColor(hex: 0x343438) + } + static var highlightColor : UIColor { + return UIColor(hex: 0xDD9224) + } + static var lineColor : UIColor { + return UIColor(hex: 0xD7D7D8) + } + static var titleColor : UIColor { + return UIColor(hex: 0x4179A9) + } + + // RGB + convenience init(_ r: Double, _ g: Double, _ b: Double, _ a: Double) { + self.init(r/255, g/255, b/255, a) + } + + // Hex to RGB + convenience init(red: Int, green: Int, blue: Int) { + assert(red >= 0 && red <= 255, "Invalid red component") + assert(green >= 0 && green <= 255, "Invalid green component") + assert(blue >= 0 && blue <= 255, "Invalid blue component") + self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) + } + convenience init(hex:Int) { + self.init(red:(hex >> 16) & 0xff, green:(hex >> 8) & 0xff, blue:hex & 0xff) + } +} + From 60dd3242390cc1dbb180d687aeb2203fa04cc5cf Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 10:52:03 -0200 Subject: [PATCH 03/37] DropShadowNavigationBar --- .../DesafioIOS.xcodeproj/project.pbxproj | 4 ++++ .../Core/DropShadowNavigationBar.swift | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 DesafioIOS/DesafioIOS/Core/DropShadowNavigationBar.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index 4b522cb..2ec5ed4 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */; }; 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */; }; 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */; }; + 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -50,6 +51,7 @@ 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Swift+Extensions.swift"; sourceTree = ""; }; 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Extensions.swift"; sourceTree = ""; }; 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; + 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowNavigationBar.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -175,6 +177,7 @@ 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */, 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */, 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */, + 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */, ); name = Utils; sourceTree = ""; @@ -431,6 +434,7 @@ buildActionMask = 2147483647; files = ( 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, + 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */, 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */, 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */, diff --git a/DesafioIOS/DesafioIOS/Core/DropShadowNavigationBar.swift b/DesafioIOS/DesafioIOS/Core/DropShadowNavigationBar.swift new file mode 100644 index 0000000..5e332f8 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/DropShadowNavigationBar.swift @@ -0,0 +1,23 @@ +// +// DropShadowNavigationBar.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit + +class DropShadowNavigationBar : UINavigationBar { + + override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + self.layer.shadowColor = UIColor.black.cgColor + self.layer.shadowOpacity = 0.2 + self.layer.shadowOffset = CGSize(width: 0, height: 4) + let shadowPath = CGRect(x: self.layer.bounds.origin.x - 10, y: self.layer.bounds.size.height - 6, width: self.layer.bounds.size.width + 20, height: 5) + self.layer.shadowPath = UIBezierPath(rect: shadowPath).cgPath + self.layer.shouldRasterize = true + } +} + From eb976338effb65ed84b35f93847e0f7874eabd7d Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:06:03 -0200 Subject: [PATCH 04/37] RestClient --- .../DesafioIOS.xcodeproj/project.pbxproj | 16 +++++++ DesafioIOS/DesafioIOS/Core/RestClient.swift | 47 +++++++++++++++++++ DesafioIOS/Podfile | 10 +++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 DesafioIOS/DesafioIOS/Core/RestClient.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index 2ec5ed4..2d60e9d 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */; }; 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */; }; 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */; }; + 4A993A781FA4B7D600A23610 /* RestClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A771FA4B7D600A23610 /* RestClient.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -52,6 +53,7 @@ 4A993A6D1FA4B40C00A23610 /* UIKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Extensions.swift"; sourceTree = ""; }; 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowNavigationBar.swift; sourceTree = ""; }; + 4A993A771FA4B7D600A23610 /* RestClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestClient.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -132,6 +134,7 @@ isa = PBXGroup; children = ( 4A993A671FA4B31300A23610 /* Managers */, + 4A993A761FA4B7C700A23610 /* External Data */, 4A993A6A1FA4B3E200A23610 /* Utils */, 4A993A601FA4B12E00A23610 /* Supporting Files */, ); @@ -182,6 +185,14 @@ name = Utils; sourceTree = ""; }; + 4A993A761FA4B7C700A23610 /* External Data */ = { + isa = PBXGroup; + children = ( + 4A993A771FA4B7D600A23610 /* RestClient.swift */, + ); + name = "External Data"; + sourceTree = ""; + }; 7FB0D43E8B06B528EC37013F /* Pods */ = { isa = PBXGroup; children = ( @@ -320,6 +331,7 @@ "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/ReachabilitySwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", + "${BUILT_PRODUCTS_DIR}/StoryboardContext/StoryboardContext.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -328,6 +340,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReachabilitySwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StoryboardContext.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -397,6 +410,7 @@ "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/ReachabilitySwift.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", + "${BUILT_PRODUCTS_DIR}/StoryboardContext/StoryboardContext.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -405,6 +419,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReachabilitySwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StoryboardContext.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -433,6 +448,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4A993A781FA4B7D600A23610 /* RestClient.swift in Sources */, 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */, 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, diff --git a/DesafioIOS/DesafioIOS/Core/RestClient.swift b/DesafioIOS/DesafioIOS/Core/RestClient.swift new file mode 100644 index 0000000..3d6b8cf --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/RestClient.swift @@ -0,0 +1,47 @@ +// +// RestClient.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation +import Alamofire + +class RestClient : NSObject { + + typealias Callback = (Bool, Any?) -> Void + + class func sendRequest(url: String, completion: @escaping Callback) { + print("Rest Client calling --> \(url)") + Alamofire + .request(url) + .validate() + .responseJSON { response in + switch response.result { + case .success: + print("RestClient -> call succeed") + //print(response.result.value ?? "No result value") + completion(true, response.result.value) + case .failure(let error): + print("RestClient -> call failed") + print(error) + completion(false, error) + } + } + } + + // MARK: - Repositories + class func repositories(page: Int=1, completion: @escaping Callback) { + let urlWithParams = String(format: "https://api.github.com/search/repositories?q=language:Java&sort=stars&page=%i", page) + RestClient.sendRequest(url: urlWithParams, completion: completion) + } + + // MARK: - Pull Requests + class func pullRequests(owner: String, repository: String, completion: @escaping Callback) { + let urlWithParams = String(format: "https://api.github.com/repos/%@/%@/pulls", owner, repository) + RestClient.sendRequest(url: urlWithParams, completion: completion) + } +} + diff --git a/DesafioIOS/Podfile b/DesafioIOS/Podfile index 7cb3ec8..8f40857 100755 --- a/DesafioIOS/Podfile +++ b/DesafioIOS/Podfile @@ -1,19 +1,27 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '9.0' +platform :ios, '8.0' use_frameworks! def shared_pods + + # Common Pods pod 'SDWebImage', '~> 3.7.3' pod 'SVProgressHUD' pod 'Alamofire' pod 'ReachabilitySwift', '~> 3' + + # Felipe Ricieri's Pods pod 'CoolDesignables' + pod 'StoryboardContext' + end +# Targeting main project target "DesafioIOS" do shared_pods end +# Targeting test project target "DesafioIOSTests" do shared_pods end From 4497714deb3ed4be16155c6b2cc16b350ac81583 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:11:50 -0200 Subject: [PATCH 05/37] Models & Services --- .../DesafioIOS.xcodeproj/project.pbxproj | 20 ++++++++++ .../DesafioIOS/Core/PullRequestService.swift | 38 ++++++++++++++++++ .../DesafioIOS/Core/RepositoryService.swift | 39 +++++++++++++++++++ DesafioIOS/DesafioIOS/Models/Owner.swift | 26 +++++++++++++ .../DesafioIOS/Models/PullRequest.swift | 34 ++++++++++++++++ DesafioIOS/DesafioIOS/Models/Repository.swift | 37 ++++++++++++++++++ 6 files changed, 194 insertions(+) create mode 100644 DesafioIOS/DesafioIOS/Core/PullRequestService.swift create mode 100644 DesafioIOS/DesafioIOS/Core/RepositoryService.swift create mode 100644 DesafioIOS/DesafioIOS/Models/Owner.swift create mode 100644 DesafioIOS/DesafioIOS/Models/PullRequest.swift create mode 100644 DesafioIOS/DesafioIOS/Models/Repository.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index 2d60e9d..7017bfe 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -20,6 +20,11 @@ 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */; }; 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */; }; 4A993A781FA4B7D600A23610 /* RestClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A771FA4B7D600A23610 /* RestClient.swift */; }; + 4A993A7A1FA4B85100A23610 /* PullRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A791FA4B85100A23610 /* PullRequest.swift */; }; + 4A993A7C1FA4B86200A23610 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A7B1FA4B86200A23610 /* Repository.swift */; }; + 4A993A7E1FA4B86700A23610 /* Owner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A7D1FA4B86700A23610 /* Owner.swift */; }; + 4A993A801FA4B8B600A23610 /* RepositoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A7F1FA4B8B600A23610 /* RepositoryService.swift */; }; + 4A993A821FA4B8C200A23610 /* PullRequestService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A811FA4B8C200A23610 /* PullRequestService.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -54,6 +59,11 @@ 4A993A6F1FA4B41900A23610 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 4A993A741FA4B4CF00A23610 /* DropShadowNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowNavigationBar.swift; sourceTree = ""; }; 4A993A771FA4B7D600A23610 /* RestClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestClient.swift; sourceTree = ""; }; + 4A993A791FA4B85100A23610 /* PullRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequest.swift; sourceTree = ""; }; + 4A993A7B1FA4B86200A23610 /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; + 4A993A7D1FA4B86700A23610 /* Owner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Owner.swift; sourceTree = ""; }; + 4A993A7F1FA4B8B600A23610 /* RepositoryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryService.swift; sourceTree = ""; }; + 4A993A811FA4B8C200A23610 /* PullRequestService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestService.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -153,6 +163,9 @@ 4A993A631FA4B12E00A23610 /* Models */ = { isa = PBXGroup; children = ( + 4A993A7B1FA4B86200A23610 /* Repository.swift */, + 4A993A791FA4B85100A23610 /* PullRequest.swift */, + 4A993A7D1FA4B86700A23610 /* Owner.swift */, ); path = Models; sourceTree = ""; @@ -189,6 +202,8 @@ isa = PBXGroup; children = ( 4A993A771FA4B7D600A23610 /* RestClient.swift */, + 4A993A7F1FA4B8B600A23610 /* RepositoryService.swift */, + 4A993A811FA4B8C200A23610 /* PullRequestService.swift */, ); name = "External Data"; sourceTree = ""; @@ -451,10 +466,15 @@ 4A993A781FA4B7D600A23610 /* RestClient.swift in Sources */, 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */, + 4A993A801FA4B8B600A23610 /* RepositoryService.swift in Sources */, 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */, 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */, + 4A993A7A1FA4B85100A23610 /* PullRequest.swift in Sources */, + 4A993A7C1FA4B86200A23610 /* Repository.swift in Sources */, + 4A993A7E1FA4B86700A23610 /* Owner.swift in Sources */, 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */, + 4A993A821FA4B8C200A23610 /* PullRequestService.swift in Sources */, 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DesafioIOS/DesafioIOS/Core/PullRequestService.swift b/DesafioIOS/DesafioIOS/Core/PullRequestService.swift new file mode 100644 index 0000000..7758430 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/PullRequestService.swift @@ -0,0 +1,38 @@ +// +// PullRequestService.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class PullRequestService : NSObject { + + func load(owner: String, repository: String, succeed: @escaping ([PullRequest]) -> Void, failed: @escaping (String) -> Void) { + RestClient.pullRequests(owner: owner, repository: repository) { (didSucceed, data) in + if didSucceed { + if let json = data as? [[String:Any]] { + var results = [PullRequest]() + for item in json { + let object = PullRequest(data: item) + results.append(object) + } + succeed(results) + } + else { + failed("Não foi possível carregar os dados desta requisição") + } + } + else { + if let error = data as? Error { + failed(error.localizedDescription) + } + else { + failed("Ocorreu um erro desconhecido.") + } + } + } + } +} diff --git a/DesafioIOS/DesafioIOS/Core/RepositoryService.swift b/DesafioIOS/DesafioIOS/Core/RepositoryService.swift new file mode 100644 index 0000000..3d4874f --- /dev/null +++ b/DesafioIOS/DesafioIOS/Core/RepositoryService.swift @@ -0,0 +1,39 @@ +// +// RepositoryService.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class RepositoryService : NSObject { + + func load(page: Int=1, succeed: @escaping ([Repository]) -> Void, failed: @escaping (String) -> Void) { + RestClient.repositories(page: page) { (didSucceed, data) in + if didSucceed { + if let json = data as? [String:Any], + let items = json["items"] as? [[String:Any]] { + var results = [Repository]() + for item in items { + let object = Repository(data: item) + results.append(object) + } + succeed(results) + } + else { + failed("Não foi possível carregar os dados desta requisição") + } + } + else { + if let error = data as? Error { + failed(error.localizedDescription) + } + else { + failed("Ocorreu um erro desconhecido.") + } + } + } + } +} diff --git a/DesafioIOS/DesafioIOS/Models/Owner.swift b/DesafioIOS/DesafioIOS/Models/Owner.swift new file mode 100644 index 0000000..606137d --- /dev/null +++ b/DesafioIOS/DesafioIOS/Models/Owner.swift @@ -0,0 +1,26 @@ +// +// Owner.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class Owner : NSObject { + + var id : String = "" + var name : String = "" + var username : String = "" + var picture : String = "" + + init(data: [String:Any]) { + + self.id = (data["id"] as? String).unwrapOrElse("") + self.name = (data["login"] as? String).unwrapOrElse("") + self.username = (data["login"] as? String).unwrapOrElse("") + self.picture = (data["avatar_url"] as? String).unwrapOrElse("") + } +} + diff --git a/DesafioIOS/DesafioIOS/Models/PullRequest.swift b/DesafioIOS/DesafioIOS/Models/PullRequest.swift new file mode 100644 index 0000000..f3fb7ee --- /dev/null +++ b/DesafioIOS/DesafioIOS/Models/PullRequest.swift @@ -0,0 +1,34 @@ +// +// PullRequest.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class PullRequest : NSObject { + + var id : Int = 0 + var title : String = "" + var objectDescription : String = "" + var state : String = "" + var htmlUrl : String = "" + var owner : Owner? = nil + + init(data: [String:Any]) { + + self.id = (data["id"] as? Int).unwrapOrElse(0) + self.title = (data["title"] as? String).unwrapOrElse("") + self.objectDescription = (data["body"] as? String).unwrapOrElse("") + self.state = (data["state"] as? String).unwrapOrElse("") + self.htmlUrl = (data["html_url"] as? String).unwrapOrElse("") + + // Owner + if let owner = data["user"] as? [String:Any] { + self.owner = Owner(data: owner) + } + } +} + diff --git a/DesafioIOS/DesafioIOS/Models/Repository.swift b/DesafioIOS/DesafioIOS/Models/Repository.swift new file mode 100644 index 0000000..2c05924 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Models/Repository.swift @@ -0,0 +1,37 @@ +// +// Repository.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class Repository : NSObject { + + var id : Int = 0 + var name : String = "" + var fullName : String = "" + var objectDescription : String = "" + var forks : Int = 0 + var stars : Int = 0 + var owner : Owner? = nil + + init(data: [String:Any]) { + + self.id = (data["id"] as? Int).unwrapOrElse(0) + self.name = (data["name"] as? String).unwrapOrElse("") + self.fullName = (data["full_name"] as? String).unwrapOrElse("") + self.objectDescription = (data["description"] as? String).unwrapOrElse("") + self.forks = (data["forks_count"] as? Int).unwrapOrElse(0) + self.stars = (data["stargazers_count"] as? Int).unwrapOrElse(0) + + // Owner + if let owner = data["owner"] as? [String:Any] { + self.owner = Owner(data: owner) + } + } +} + + From 19e1ea81095fbfdb80d5cd7b17faca2be5f012e1 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:19:11 -0200 Subject: [PATCH 06/37] Controllers & Cells --- .../DesafioIOS.xcodeproj/project.pbxproj | 48 ++++ .../Controllers/MainStoryboard.swift | 22 ++ .../PullRequestTableViewCell.swift | 9 + .../PullRequestsViewController.swift | 240 +++++++++++++++++ .../RepositoriesViewController.swift | 241 ++++++++++++++++++ .../Controllers/RepositoryTableViewCell.swift | 9 + .../Controllers/WebViewController.swift | 160 ++++++++++++ 7 files changed, 729 insertions(+) create mode 100644 DesafioIOS/DesafioIOS/Controllers/MainStoryboard.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/PullRequestTableViewCell.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/PullRequestsViewController.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/RepositoriesViewController.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/RepositoryTableViewCell.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/WebViewController.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index 7017bfe..9182f12 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -25,6 +25,12 @@ 4A993A7E1FA4B86700A23610 /* Owner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A7D1FA4B86700A23610 /* Owner.swift */; }; 4A993A801FA4B8B600A23610 /* RepositoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A7F1FA4B8B600A23610 /* RepositoryService.swift */; }; 4A993A821FA4B8C200A23610 /* PullRequestService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A811FA4B8C200A23610 /* PullRequestService.swift */; }; + 4A993A841FA4B9DA00A23610 /* MainStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A831FA4B9DA00A23610 /* MainStoryboard.swift */; }; + 4A993A861FA4BA0E00A23610 /* RepositoriesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A851FA4BA0E00A23610 /* RepositoriesViewController.swift */; }; + 4A993A881FA4BA1D00A23610 /* PullRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A871FA4BA1D00A23610 /* PullRequestsViewController.swift */; }; + 4A993A8A1FA4BA3F00A23610 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A891FA4BA3F00A23610 /* WebViewController.swift */; }; + 4A993A8F1FA4BAC100A23610 /* RepositoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */; }; + 4A993A911FA4BACD00A23610 /* PullRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -64,6 +70,12 @@ 4A993A7D1FA4B86700A23610 /* Owner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Owner.swift; sourceTree = ""; }; 4A993A7F1FA4B8B600A23610 /* RepositoryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryService.swift; sourceTree = ""; }; 4A993A811FA4B8C200A23610 /* PullRequestService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestService.swift; sourceTree = ""; }; + 4A993A831FA4B9DA00A23610 /* MainStoryboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStoryboard.swift; sourceTree = ""; }; + 4A993A851FA4BA0E00A23610 /* RepositoriesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoriesViewController.swift; sourceTree = ""; }; + 4A993A871FA4BA1D00A23610 /* PullRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestsViewController.swift; sourceTree = ""; }; + 4A993A891FA4BA3F00A23610 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; + 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryTableViewCell.swift; sourceTree = ""; }; + 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestTableViewCell.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -136,6 +148,10 @@ 4A993A5E1FA4B12E00A23610 /* Controllers */ = { isa = PBXGroup; children = ( + 4A993A831FA4B9DA00A23610 /* MainStoryboard.swift */, + 4A993A931FA4BB0E00A23610 /* Repositories */, + 4A993A921FA4BB0700A23610 /* PullRequests */, + 4A993A941FA4BB3900A23610 /* Web */, ); path = Controllers; sourceTree = ""; @@ -208,6 +224,32 @@ name = "External Data"; sourceTree = ""; }; + 4A993A921FA4BB0700A23610 /* PullRequests */ = { + isa = PBXGroup; + children = ( + 4A993A871FA4BA1D00A23610 /* PullRequestsViewController.swift */, + 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */, + ); + name = PullRequests; + sourceTree = ""; + }; + 4A993A931FA4BB0E00A23610 /* Repositories */ = { + isa = PBXGroup; + children = ( + 4A993A851FA4BA0E00A23610 /* RepositoriesViewController.swift */, + 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */, + ); + name = Repositories; + sourceTree = ""; + }; + 4A993A941FA4BB3900A23610 /* Web */ = { + isa = PBXGroup; + children = ( + 4A993A891FA4BA3F00A23610 /* WebViewController.swift */, + ); + name = Web; + sourceTree = ""; + }; 7FB0D43E8B06B528EC37013F /* Pods */ = { isa = PBXGroup; children = ( @@ -464,16 +506,22 @@ buildActionMask = 2147483647; files = ( 4A993A781FA4B7D600A23610 /* RestClient.swift in Sources */, + 4A993A861FA4BA0E00A23610 /* RepositoriesViewController.swift in Sources */, 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */, + 4A993A911FA4BACD00A23610 /* PullRequestTableViewCell.swift in Sources */, 4A993A801FA4B8B600A23610 /* RepositoryService.swift in Sources */, 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, + 4A993A881FA4BA1D00A23610 /* PullRequestsViewController.swift in Sources */, 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */, 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */, + 4A993A8F1FA4BAC100A23610 /* RepositoryTableViewCell.swift in Sources */, 4A993A7A1FA4B85100A23610 /* PullRequest.swift in Sources */, 4A993A7C1FA4B86200A23610 /* Repository.swift in Sources */, 4A993A7E1FA4B86700A23610 /* Owner.swift in Sources */, + 4A993A8A1FA4BA3F00A23610 /* WebViewController.swift in Sources */, 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */, + 4A993A841FA4B9DA00A23610 /* MainStoryboard.swift in Sources */, 4A993A821FA4B8C200A23610 /* PullRequestService.swift in Sources */, 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */, ); diff --git a/DesafioIOS/DesafioIOS/Controllers/MainStoryboard.swift b/DesafioIOS/DesafioIOS/Controllers/MainStoryboard.swift new file mode 100644 index 0000000..f91e575 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/MainStoryboard.swift @@ -0,0 +1,22 @@ +// +// MainStoryboard.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import StoryboardContext + +class MainStoryboard : StoryboardContext { + + struct Segue { + static let toPullRequest = "toPullRequests" + static let toWebView = "toWebView" + } + + convenience override init() { + self.init(name: "Main") + } +} + diff --git a/DesafioIOS/DesafioIOS/Controllers/PullRequestTableViewCell.swift b/DesafioIOS/DesafioIOS/Controllers/PullRequestTableViewCell.swift new file mode 100644 index 0000000..d138999 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/PullRequestTableViewCell.swift @@ -0,0 +1,9 @@ +// +// PullRequestTableViewCell.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation diff --git a/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewController.swift b/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewController.swift new file mode 100644 index 0000000..850eff5 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewController.swift @@ -0,0 +1,240 @@ +// +// PullRequestsViewController.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit +import SVProgressHUD + +class PullRequestsViewController : UITableViewController { + + public var repository : Repository? + + @IBOutlet weak var tableHeader : UIView? + @IBOutlet weak var pullRequestsCountLabel : UILabel? + + fileprivate(set) public var source = [PullRequest]() + fileprivate(set) public var isProcessing = false + fileprivate(set) public var openPullsCount = 0 + fileprivate(set) public var closedPullsCount = 0 + + // Setup + fileprivate func setup() { + + // Title + if let safeRepository = self.repository { + self.title = safeRepository.name + self.navigationItem.title = safeRepository.name + } + + // Table header line + if let header = self.tableHeader { + let bottomBorder = CALayer() + bottomBorder.frame = CGRect(x: 0, y: header.frame.size.height - 1.0, width: header.frame.size.width, height: 1) + bottomBorder.backgroundColor = UIColor.lineColor.cgColor + header.layer.addSublayer(bottomBorder) + } + + // Open/Close repos label + self.pullRequestsCountLabel?.text = "" + + // Refresh Control + self.refreshControl = UIRefreshControl() + self.refreshControl!.tintColor = UIColor.navigationBarColor + self.refreshControl!.attributedTitle = nil + self.refreshControl!.addTarget(self, action: #selector(PullRequestsViewController.refresh), for: .valueChanged) + self.tableView.addSubview(refreshControl!) + + // Table cell height + self.tableView.rowHeight = UITableViewAutomaticDimension + self.tableView.estimatedRowHeight = 140 + + // Load Data + self.triggerRefreshControl() + } + + // MARK: - Lifecycle Methods + override func viewDidLoad() { + super.viewDidLoad() + self.setup() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.addObservers() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.removeObservers() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == MainStoryboard.Segue.toWebView { + let nav = segue.destination as! UINavigationController + let vc = nav.viewControllers.first as! WebViewController + vc.pullRequest = sender as? PullRequest + } + } + + // MARK: - Observers & Notifications + public func addObservers() { + NotificationCenter.default.addObserver(self, selector: #selector(PullRequestsViewController.notificationIsReachable(n:)), name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(PullRequestsViewController.notificationNotReachable(n:)), name: NotificationCenter.Name.NotReachable, object: nil) + } + + public func removeObservers() { + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.NotReachable, object: nil) + } + + public func notificationIsReachable(n: Notification) { + if self.source.count == 0 { + if !self.isProcessing { + self.triggerRefreshControl() + } + } + } + + public func notificationNotReachable(n: Notification) { + SVProgressHUD.showError(withStatus: "Você está desconectado") + } + + // MARK: - Data Methods + func refresh() { + if !self.isProcessing { + self.isProcessing = true + // Re-fetch + self.fetchData() + } + } + + func triggerRefreshControl() { + if let safeControl = self.refreshControl { + safeControl.beginRefreshing() + self.tableView.setContentOffset(CGPoint(x: 0, y: -safeControl.frame.size.height), animated: true) + } + self.refresh() + } + + func fetchData(completion: (() -> Void)?=nil) { + + if let safeRepository = self.repository, + let safeOwner = safeRepository.owner { + PullRequest.load(owner: safeOwner.username, repository: safeRepository.name, succeed: { + [weak self] results in + + if let this = self { + // Check state + for pull in results { + // Open pulls + if pull.state == "open" { + this.openPullsCount += 1 + } + // Closed pulls + if pull.state == "closed" { + this.closedPullsCount += 1 + } + } + // Fill label + let openText = "\(this.openPullsCount) opened" + let text = "\(openText) / \(this.closedPullsCount) closed" + let attrStr = NSMutableAttributedString(string: text) + attrStr.addAttribute(NSForegroundColorAttributeName, value: UIColor.highlightColor, range: NSMakeRange(0, openText.characters.count)) + this.pullRequestsCountLabel?.attributedText = attrStr + // Fill source + this.source = results + this.tableView.reloadData() + this.refreshControl?.endRefreshing() + this.isProcessing = false + completion?() + } + + }) { [weak self] errorDescription in + // Show error + SVProgressHUD.showError(withStatus: errorDescription) + if let this = self { + this.refreshControl?.endRefreshing() + this.isProcessing = false + } + completion?() + } + } + else { + SVProgressHUD.showError(withStatus: "Não foi possível carregar os dados desse repositório.") + self.refreshControl?.endRefreshing() + completion?() + } + } + + // MARK: - IB Actions + @IBAction func actionBack() { + let _ = self.navigationController?.popViewController(animated: true) + } +} + +// MARK: - Table Methods +extension PullRequestsViewController { + + // Rows + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: PullRequestCell.objectID, for: indexPath) as! PullRequestCell + let object = self.source[indexPath.row] + + cell.fillData(object: object) + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let object = self.source[indexPath.row] + self.performSegue(withIdentifier: MainStoryboard.Segue.toWebView, sender: object) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.source.count + } + + // Sections + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } +} + +// MARK: - Resources +class PullRequestCell : UITableViewCell { + + static let objectID = "pullRequestCell" + + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var userNicknameLabel: UILabel! + @IBOutlet weak var userNameLabel: UILabel! + @IBOutlet weak var userPicture: UIImageView! + + func fillData(object: PullRequest) { + // Repository Data + self.nameLabel.text = object.title + self.descriptionLabel.text = object.objectDescription + // Owner Data + if let owner = object.owner { + self.userNameLabel.text = owner.name + self.userNicknameLabel.text = owner.username + if owner.picture != "", + let url = URL(string: owner.picture), + let placeholder = UIImage(named: "avatar_noimage") { + self.userPicture.sd_setImage(with: url, placeholderImage: placeholder) + // Image Configuration + self.userPicture.layer.cornerRadius = 18 + self.userPicture.layer.masksToBounds = true + } + } + } +} + + diff --git a/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewController.swift b/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewController.swift new file mode 100644 index 0000000..64e4abe --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewController.swift @@ -0,0 +1,241 @@ +// +// RepositoriesViewController.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit +import SDWebImage +import SVProgressHUD + +class RepositoriesViewController : UITableViewController { + + @IBOutlet weak var infiniteScrollingView: UIActivityIndicatorView? + + fileprivate(set) public var source = [Repository]() + fileprivate(set) public var page = 1 + fileprivate(set) public var isProcessing = false + + // Setup + fileprivate func setup() { + // Table cell height + self.tableView.rowHeight = UITableViewAutomaticDimension + self.tableView.estimatedRowHeight = 130 + + // Infinite Scrolling + self.infiniteScrollingView?.alpha = 0 + + // Refresh Control + self.refreshControl = UIRefreshControl() + self.refreshControl!.tintColor = UIColor.navigationBarColor + self.refreshControl!.attributedTitle = nil + self.refreshControl!.addTarget(self, action: #selector(RepositoriesViewController.refresh), for: .valueChanged) + self.tableView.addSubview(refreshControl!) + + // Load Repositories + self.triggerRefreshControl() + } + + // MARK: - Lifecycle Methods + override func viewDidLoad() { + super.viewDidLoad() + self.setup() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.addObservers() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.removeObservers() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == MainStoryboard.Segue.toPullRequest { + let vc = segue.destination as! PullRequestsViewController + vc.repository = sender as? Repository + } + } + + // MARK: - Observers & Notifications + public func addObservers() { + NotificationCenter.default.addObserver(self, selector: #selector(RepositoriesViewController.notificationIsReachable(n:)), name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(RepositoriesViewController.notificationNotReachable(n:)), name: NotificationCenter.Name.NotReachable, object: nil) + } + + public func removeObservers() { + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.NotReachable, object: nil) + } + + public func notificationIsReachable(n: Notification) { + if self.source.count == 0 { + if !self.isProcessing { + self.triggerRefreshControl() + } + } + } + + public func notificationNotReachable(n: Notification) { + SVProgressHUD.showError(withStatus: "Você está desconectado") + } + + // MARK: - Data Methods + func refresh() { + if !self.isProcessing { + self.isProcessing = true + + // Reset + self.page = 1 + + // Re-fetch + self.fetchData() { + [weak self] in + if let this = self { + this.refreshControl?.endRefreshing() + } + } + } + } + + func triggerRefreshControl() { + if let safeControl = self.refreshControl { + safeControl.beginRefreshing() + self.tableView.setContentOffset(CGPoint(x: 0, y: -safeControl.frame.size.height), animated: true) + } + self.refresh() + } + + func fetchData(completion: (() -> Void)?=nil) { + + Repository.load(page: self.page, succeed: { + [weak self] results in + + if let this = self { + if this.page == 1 { + this.source = [] + } + this.source.append(contentsOf: results) + this.tableView.reloadData() + this.isProcessing = false + completion?() + } + + }) { [weak self] errorDescription in + // Show error + SVProgressHUD.showError(withStatus: errorDescription) + if let this = self { + this.refreshControl?.endRefreshing() + this.isProcessing = false + } + } + } +} + +// MARK: - Infinite Scrolling +extension RepositoriesViewController { + + func triggerInfiniteScrolling(completion: (()->Void)?=nil) { + if !self.isProcessing { + self.isProcessing = true + self.infiniteScrollingView?.alpha = 1 + self.page += 1 + self.fetchData() { + [weak self] in + if let this = self { + this.infiniteScrollingView?.alpha = 0 + completion?() + } + } + } + else { + completion?() + } + } +} + +// MARK: - Table Methods +extension RepositoriesViewController { + + // Rows + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: RepositoryCell.objectID, for: indexPath) as! RepositoryCell + let object = self.source[indexPath.row] + + cell.fillData(object: object) + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let object = self.source[indexPath.row] + self.performSegue(withIdentifier: MainStoryboard.Segue.toPullRequest, sender: object) + } + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row == (self.source.count - 1) { + self.triggerInfiniteScrolling() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.source.count + } + + // Sections + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } +} + +// MARK: - Resources +class RepositoryCell : UITableViewCell { + + static let objectID = "repositoryCell" + + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var forksCountLabel: UILabel! + @IBOutlet weak var starsCountLabel: UILabel! + @IBOutlet weak var userNicknameLabel: UILabel! + @IBOutlet weak var userNameLabel: UILabel! + @IBOutlet weak var userPicture: UIImageView! + + func fillData(object: Repository) { + + // Repository Data + self.nameLabel.text = object.fullName + self.descriptionLabel.text = object.objectDescription + + // Format value + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.groupingSeparator = "." + formatter.usesGroupingSeparator = true + let forksNumber = NSNumber(value: object.forks) + let starsNumber = NSNumber(value: object.stars) + self.forksCountLabel.text = formatter.string(from: forksNumber) + self.starsCountLabel.text = formatter.string(from: starsNumber) + + // Owner Data + if let owner = object.owner { + self.userNameLabel.text = owner.name + self.userNicknameLabel.text = owner.username + if owner.picture != "", + let url = URL(string: owner.picture), + let placeholder = UIImage(named: "avatar_noimage") { + self.userPicture.sd_setImage(with: url, placeholderImage: placeholder) + // Image Configuration + self.userPicture.layer.cornerRadius = 20 + } + } + } +} + + diff --git a/DesafioIOS/DesafioIOS/Controllers/RepositoryTableViewCell.swift b/DesafioIOS/DesafioIOS/Controllers/RepositoryTableViewCell.swift new file mode 100644 index 0000000..425ffee --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/RepositoryTableViewCell.swift @@ -0,0 +1,9 @@ +// +// RepositoryTableViewCell.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation diff --git a/DesafioIOS/DesafioIOS/Controllers/WebViewController.swift b/DesafioIOS/DesafioIOS/Controllers/WebViewController.swift new file mode 100644 index 0000000..fd1b0f1 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/WebViewController.swift @@ -0,0 +1,160 @@ +// +// WebViewController.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import UIKit +import SVProgressHUD + +class WebViewController : UIViewController { + + public var pullRequest: PullRequest? + + @IBOutlet weak var webView: UIWebView? + @IBOutlet weak var activityIndicator: UIActivityIndicatorView? + @IBOutlet weak var reloadButton: UIButton? + + fileprivate(set) public var url : URL? + fileprivate(set) public var didFail = false + fileprivate(set) public var isProcessing = false + + // Setup + fileprivate func setup() { + // Primary state + self.reloadButton?.disable() + self.activityIndicator?.enable() + self.activityIndicator?.startAnimating() + self.title = "" + self.navigationItem.title = "" + } + + func launchUrl() { + // Load + if let safePull = self.pullRequest, + let url = URL(string: safePull.htmlUrl) { + + // Title + self.title = safePull.title + self.navigationItem.title = safePull.title + + // Loading WebView + self.url = url + self.loadWebView() + } + } + + // MARK: - Lifecycle Methods + override func viewDidLoad() { + super.viewDidLoad() + self.setup() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.launchUrl() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.addObservers() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.removeObservers() + } + + // MARK: - Observers & Notifications + public func addObservers() { + NotificationCenter.default.addObserver(self, selector: #selector(WebViewController.notificationIsReachable(n:)), name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(WebViewController.notificationNotReachable(n:)), name: NotificationCenter.Name.NotReachable, object: nil) + } + + public func removeObservers() { + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.Reachable, object: nil) + NotificationCenter.default.removeObserver(self, name: NotificationCenter.Name.NotReachable, object: nil) + } + + func notificationIsReachable(n: Notification) { + if self.didFail && !self.isProcessing { + self.loadWebView() + } + } + + func notificationNotReachable(n: Notification) { + SVProgressHUD.showError(withStatus: "Você está desconectado") + } + + // MARK: - Internal Methods + func loadWebView() { + if let safeUrl = self.url { + let urlRequest = URLRequest(url: safeUrl) + self.webView?.loadRequest(urlRequest) + } + else { + SVProgressHUD.showError(withStatus: "Esta Pull Request não possui HTML URL.") + } + } + + // MARK: - IB Actions + @IBAction func actionReload() { + self.loadWebView() + } + + @IBAction func actionDismiss() { + self.dismiss(animated: true, completion: nil) + } +} + +// MARK: - Webview delegate +extension WebViewController : UIWebViewDelegate { + + func webViewDidStartLoad(_ webView: UIWebView) { + self.reloadButton?.disable() + self.activityIndicator?.enable() + self.didFail = false + self.isProcessing = true + } + + func webViewDidFinishLoad(_ webView: UIWebView) { + self.activityIndicator?.disable() + self.reloadButton?.enable() + self.isProcessing = false + } + + func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { + self.activityIndicator?.disable() + self.reloadButton?.enable() + self.didFail = true + self.isProcessing = false + + // Show Error + let alert = UIAlertController(title: "Ocorreu um erro", message: error.localizedDescription, preferredStyle: .alert) + let yesAction = UIAlertAction(title: "Tentar novamente", style: .destructive) { [weak self](action) in + self?.loadWebView() + } + let noAction = UIAlertAction(title: "Fechar", style: .cancel, handler: { (action) in + // Nothing + }) + alert.addAction(yesAction) + alert.addAction(noAction) + self.present(alert, animated: true, completion: nil) + } +} + +// MARK: - Custom methods for UIView +fileprivate extension UIView { + func enable() { + self.isHidden = false + self.isUserInteractionEnabled = true + } + func disable() { + self.isHidden = true + self.isUserInteractionEnabled = false + } +} + + From dae73c1e180277541af0e3f99d717ea88229ab6f Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:20:44 -0200 Subject: [PATCH 07/37] ViewModels --- DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj | 12 ++++++++++++ .../Controllers/PullRequestsViewModel.swift | 13 +++++++++++++ .../Controllers/RepositoriesViewModel.swift | 14 ++++++++++++++ .../DesafioIOS/Controllers/WebViewModel.swift | 13 +++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 DesafioIOS/DesafioIOS/Controllers/PullRequestsViewModel.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/RepositoriesViewModel.swift create mode 100644 DesafioIOS/DesafioIOS/Controllers/WebViewModel.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index 9182f12..b3ab6eb 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ 4A993A8A1FA4BA3F00A23610 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A891FA4BA3F00A23610 /* WebViewController.swift */; }; 4A993A8F1FA4BAC100A23610 /* RepositoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */; }; 4A993A911FA4BACD00A23610 /* PullRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */; }; + 4A993A961FA4BB6300A23610 /* RepositoriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A951FA4BB6300A23610 /* RepositoriesViewModel.swift */; }; + 4A993A981FA4BB7000A23610 /* PullRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A971FA4BB7000A23610 /* PullRequestsViewModel.swift */; }; + 4A993A9A1FA4BB7B00A23610 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A991FA4BB7B00A23610 /* WebViewModel.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -76,6 +79,9 @@ 4A993A891FA4BA3F00A23610 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryTableViewCell.swift; sourceTree = ""; }; 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestTableViewCell.swift; sourceTree = ""; }; + 4A993A951FA4BB6300A23610 /* RepositoriesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoriesViewModel.swift; sourceTree = ""; }; + 4A993A971FA4BB7000A23610 /* PullRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestsViewModel.swift; sourceTree = ""; }; + 4A993A991FA4BB7B00A23610 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -228,6 +234,7 @@ isa = PBXGroup; children = ( 4A993A871FA4BA1D00A23610 /* PullRequestsViewController.swift */, + 4A993A971FA4BB7000A23610 /* PullRequestsViewModel.swift */, 4A993A901FA4BACD00A23610 /* PullRequestTableViewCell.swift */, ); name = PullRequests; @@ -237,6 +244,7 @@ isa = PBXGroup; children = ( 4A993A851FA4BA0E00A23610 /* RepositoriesViewController.swift */, + 4A993A951FA4BB6300A23610 /* RepositoriesViewModel.swift */, 4A993A8E1FA4BAC100A23610 /* RepositoryTableViewCell.swift */, ); name = Repositories; @@ -246,6 +254,7 @@ isa = PBXGroup; children = ( 4A993A891FA4BA3F00A23610 /* WebViewController.swift */, + 4A993A991FA4BB7B00A23610 /* WebViewModel.swift */, ); name = Web; sourceTree = ""; @@ -510,7 +519,9 @@ 4A427B7B1FA4AF6700B0E95D /* AppDelegate.swift in Sources */, 4A993A751FA4B4CF00A23610 /* DropShadowNavigationBar.swift in Sources */, 4A993A911FA4BACD00A23610 /* PullRequestTableViewCell.swift in Sources */, + 4A993A981FA4BB7000A23610 /* PullRequestsViewModel.swift in Sources */, 4A993A801FA4B8B600A23610 /* RepositoryService.swift in Sources */, + 4A993A9A1FA4BB7B00A23610 /* WebViewModel.swift in Sources */, 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */, 4A993A881FA4BA1D00A23610 /* PullRequestsViewController.swift in Sources */, 4A993A701FA4B41900A23610 /* Foundation+Extensions.swift in Sources */, @@ -523,6 +534,7 @@ 4A993A6E1FA4B40C00A23610 /* UIKit+Extensions.swift in Sources */, 4A993A841FA4B9DA00A23610 /* MainStoryboard.swift in Sources */, 4A993A821FA4B8C200A23610 /* PullRequestService.swift in Sources */, + 4A993A961FA4BB6300A23610 /* RepositoriesViewModel.swift in Sources */, 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewModel.swift b/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewModel.swift new file mode 100644 index 0000000..40e53bf --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/PullRequestsViewModel.swift @@ -0,0 +1,13 @@ +// +// PullRequestsViewModel.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class PullRequestsViewModel { + +} diff --git a/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewModel.swift b/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewModel.swift new file mode 100644 index 0000000..f15e375 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/RepositoriesViewModel.swift @@ -0,0 +1,14 @@ +// +// RepositoriesViewModel.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class RepositoriesViewModel { + + +} diff --git a/DesafioIOS/DesafioIOS/Controllers/WebViewModel.swift b/DesafioIOS/DesafioIOS/Controllers/WebViewModel.swift new file mode 100644 index 0000000..fb524f9 --- /dev/null +++ b/DesafioIOS/DesafioIOS/Controllers/WebViewModel.swift @@ -0,0 +1,13 @@ +// +// WebViewModel.swift +// DesafioIOS +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import Foundation + +class WebViewModel { + +} From ffe97e4c1a3c2de73c292f91f491bb61eedbe853 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:22:55 -0200 Subject: [PATCH 08/37] AppDelegate --- DesafioIOS/DesafioIOS/AppDelegate.swift | 56 +++++++++++++------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/DesafioIOS/DesafioIOS/AppDelegate.swift b/DesafioIOS/DesafioIOS/AppDelegate.swift index aee7b8c..820afbf 100644 --- a/DesafioIOS/DesafioIOS/AppDelegate.swift +++ b/DesafioIOS/DesafioIOS/AppDelegate.swift @@ -8,44 +8,44 @@ import UIKit import CoreData +import SVProgressHUD @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - + + // Setup + func buildApplication() { + + // Status bar + UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent + + // Navigation Bar custom colors + UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white] + UINavigationBar.appearance().tintColor = UIColor.white + + // ProgressHUD custom colors + SVProgressHUD.setBackgroundColor(UIColor.navigationBarColor) + SVProgressHUD.setForegroundColor(UIColor.white) + } + + // MARK: - Lifecycle Methods func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + buildApplication() return true } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - + + // MARK: - Reachability Listeners func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + ReachabilityManager.subscribe() } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - // Saves changes in the application's managed object context before the application terminates. - self.saveContext() + + func applicationDidEnterBackground(_ application: UIApplication) { + ReachabilityManager.unsubscribe() } // MARK: - Core Data stack - lazy var persistentContainer: NSPersistentContainer = { /* The persistent container for the application. This implementation @@ -74,7 +74,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }() // MARK: - Core Data Saving support - func saveContext () { let context = persistentContainer.viewContext if context.hasChanges { @@ -89,5 +88,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + // Saves changes in the application's managed object context before the application terminates. + self.saveContext() + } } From a3e70a76f6cf43de72b570318bb0c084afde4855 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:29:20 -0200 Subject: [PATCH 09/37] Tests Implemented --- .../DesafioIOS.xcodeproj/project.pbxproj | 70 +++++- .../DesafioIOSTests/DesafioIOSTests.swift | 36 --- DesafioIOS/DesafioIOSTests/OwnerTests.swift | 33 +++ .../DesafioIOSTests/PullRequestTests.swift | 65 ++++++ .../PullRequestsViewControllerTests.swift | 205 +++++++++++++++++ .../ReachabilityManagerTests.swift | 41 ++++ .../RepositoriesViewControllerTests.swift | 216 ++++++++++++++++++ .../DesafioIOSTests/RepositoryTests.swift | 65 ++++++ .../DesafioIOSTests/RestClientTests.swift | 34 +++ .../WebViewControllerTests.swift | 134 +++++++++++ 10 files changed, 858 insertions(+), 41 deletions(-) delete mode 100644 DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/OwnerTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/PullRequestTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/PullRequestsViewControllerTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/ReachabilityManagerTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/RepositoriesViewControllerTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/RepositoryTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/RestClientTests.swift create mode 100644 DesafioIOS/DesafioIOSTests/WebViewControllerTests.swift diff --git a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj index b3ab6eb..92d6572 100644 --- a/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj +++ b/DesafioIOS/DesafioIOS.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 4A427B831FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B811FA4AF6700B0E95D /* DesafioIOS.xcdatamodeld */; }; 4A427B851FA4AF6700B0E95D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A427B841FA4AF6700B0E95D /* Assets.xcassets */; }; 4A427B881FA4AF6700B0E95D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A427B861FA4AF6700B0E95D /* LaunchScreen.storyboard */; }; - 4A427B931FA4AF6700B0E95D /* DesafioIOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */; }; 4A993A641FA4B12E00A23610 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4A993A621FA4B12E00A23610 /* Info.plist */; }; 4A993A691FA4B32200A23610 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A681FA4B32200A23610 /* ReachabilityManager.swift */; }; 4A993A6C1FA4B3F500A23610 /* Swift+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A6B1FA4B3F500A23610 /* Swift+Extensions.swift */; }; @@ -34,6 +33,14 @@ 4A993A961FA4BB6300A23610 /* RepositoriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A951FA4BB6300A23610 /* RepositoriesViewModel.swift */; }; 4A993A981FA4BB7000A23610 /* PullRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A971FA4BB7000A23610 /* PullRequestsViewModel.swift */; }; 4A993A9A1FA4BB7B00A23610 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A991FA4BB7B00A23610 /* WebViewModel.swift */; }; + 4A993A9F1FA4BC9300A23610 /* ReachabilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993A9E1FA4BC9300A23610 /* ReachabilityManagerTests.swift */; }; + 4A993AA11FA4BCB200A23610 /* RestClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AA01FA4BCB200A23610 /* RestClientTests.swift */; }; + 4A993AA41FA4BCF700A23610 /* RepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AA31FA4BCF700A23610 /* RepositoryTests.swift */; }; + 4A993AA61FA4BD0600A23610 /* PullRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AA51FA4BD0600A23610 /* PullRequestTests.swift */; }; + 4A993AA81FA4BD1B00A23610 /* OwnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AA71FA4BD1B00A23610 /* OwnerTests.swift */; }; + 4A993AAA1FA4BD4000A23610 /* RepositoriesViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AA91FA4BD4000A23610 /* RepositoriesViewControllerTests.swift */; }; + 4A993AAC1FA4BD6A00A23610 /* PullRequestsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AAB1FA4BD6A00A23610 /* PullRequestsViewControllerTests.swift */; }; + 4A993AAE1FA4BD7800A23610 /* WebViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A993AAD1FA4BD7800A23610 /* WebViewControllerTests.swift */; }; 4DB7243BB4F6778FA4C27123 /* Pods_DesafioIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */; }; D443C24FAA1DB184CE1DC556 /* Pods_DesafioIOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39C83602D0FE778852F685A5 /* Pods_DesafioIOSTests.framework */; }; /* End PBXBuildFile section */ @@ -58,7 +65,6 @@ 4A427B841FA4AF6700B0E95D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4A427B871FA4AF6700B0E95D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 4A427B8E1FA4AF6700B0E95D /* DesafioIOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DesafioIOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesafioIOSTests.swift; sourceTree = ""; }; 4A427B941FA4AF6700B0E95D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4A993A611FA4B12E00A23610 /* Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; 4A993A621FA4B12E00A23610 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -82,6 +88,14 @@ 4A993A951FA4BB6300A23610 /* RepositoriesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoriesViewModel.swift; sourceTree = ""; }; 4A993A971FA4BB7000A23610 /* PullRequestsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestsViewModel.swift; sourceTree = ""; }; 4A993A991FA4BB7B00A23610 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; + 4A993A9E1FA4BC9300A23610 /* ReachabilityManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityManagerTests.swift; sourceTree = ""; }; + 4A993AA01FA4BCB200A23610 /* RestClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestClientTests.swift; sourceTree = ""; }; + 4A993AA31FA4BCF700A23610 /* RepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryTests.swift; sourceTree = ""; }; + 4A993AA51FA4BD0600A23610 /* PullRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestTests.swift; sourceTree = ""; }; + 4A993AA71FA4BD1B00A23610 /* OwnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnerTests.swift; sourceTree = ""; }; + 4A993AA91FA4BD4000A23610 /* RepositoriesViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoriesViewControllerTests.swift; sourceTree = ""; }; + 4A993AAB1FA4BD6A00A23610 /* PullRequestsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullRequestsViewControllerTests.swift; sourceTree = ""; }; + 4A993AAD1FA4BD7800A23610 /* WebViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewControllerTests.swift; sourceTree = ""; }; 4BA7F1716C11BA8B1E0EB9CD /* Pods-DesafioIOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.release.xcconfig"; sourceTree = ""; }; 6B19B80C6E3CBF350F139E84 /* Pods-DesafioIOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DesafioIOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DesafioIOS/Pods-DesafioIOS.debug.xcconfig"; sourceTree = ""; }; D7C9DB27EC4D977B219219A4 /* Pods_DesafioIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DesafioIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -145,8 +159,9 @@ 4A427B911FA4AF6700B0E95D /* DesafioIOSTests */ = { isa = PBXGroup; children = ( - 4A427B921FA4AF6700B0E95D /* DesafioIOSTests.swift */, - 4A427B941FA4AF6700B0E95D /* Info.plist */, + 4A993A9B1FA4BC5D00A23610 /* Core */, + 4A993A9C1FA4BC6300A23610 /* Models */, + 4A993A9D1FA4BC6800A23610 /* Controllers */, ); path = DesafioIOSTests; sourceTree = ""; @@ -259,6 +274,44 @@ name = Web; sourceTree = ""; }; + 4A993A9B1FA4BC5D00A23610 /* Core */ = { + isa = PBXGroup; + children = ( + 4A993AA21FA4BCDB00A23610 /* Supporting Files */, + 4A993A9E1FA4BC9300A23610 /* ReachabilityManagerTests.swift */, + 4A993AA01FA4BCB200A23610 /* RestClientTests.swift */, + ); + name = Core; + sourceTree = ""; + }; + 4A993A9C1FA4BC6300A23610 /* Models */ = { + isa = PBXGroup; + children = ( + 4A993AA31FA4BCF700A23610 /* RepositoryTests.swift */, + 4A993AA51FA4BD0600A23610 /* PullRequestTests.swift */, + 4A993AA71FA4BD1B00A23610 /* OwnerTests.swift */, + ); + name = Models; + sourceTree = ""; + }; + 4A993A9D1FA4BC6800A23610 /* Controllers */ = { + isa = PBXGroup; + children = ( + 4A993AA91FA4BD4000A23610 /* RepositoriesViewControllerTests.swift */, + 4A993AAB1FA4BD6A00A23610 /* PullRequestsViewControllerTests.swift */, + 4A993AAD1FA4BD7800A23610 /* WebViewControllerTests.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + 4A993AA21FA4BCDB00A23610 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4A427B941FA4AF6700B0E95D /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 7FB0D43E8B06B528EC37013F /* Pods */ = { isa = PBXGroup; children = ( @@ -543,7 +596,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A427B931FA4AF6700B0E95D /* DesafioIOSTests.swift in Sources */, + 4A993AAA1FA4BD4000A23610 /* RepositoriesViewControllerTests.swift in Sources */, + 4A993AA81FA4BD1B00A23610 /* OwnerTests.swift in Sources */, + 4A993A9F1FA4BC9300A23610 /* ReachabilityManagerTests.swift in Sources */, + 4A993AA11FA4BCB200A23610 /* RestClientTests.swift in Sources */, + 4A993AA61FA4BD0600A23610 /* PullRequestTests.swift in Sources */, + 4A993AAC1FA4BD6A00A23610 /* PullRequestsViewControllerTests.swift in Sources */, + 4A993AA41FA4BCF700A23610 /* RepositoryTests.swift in Sources */, + 4A993AAE1FA4BD7800A23610 /* WebViewControllerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift b/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift deleted file mode 100644 index 120cdf6..0000000 --- a/DesafioIOS/DesafioIOSTests/DesafioIOSTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// DesafioIOSTests.swift -// DesafioIOSTests -// -// Created by Felipe Ricieri on 28/10/17. -// Copyright © 2017 Nexaas. All rights reserved. -// - -import XCTest -@testable import DesafioIOS - -class DesafioIOSTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/DesafioIOS/DesafioIOSTests/OwnerTests.swift b/DesafioIOS/DesafioIOSTests/OwnerTests.swift new file mode 100644 index 0000000..dad2d9d --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/OwnerTests.swift @@ -0,0 +1,33 @@ +// +// OwnerTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class OwnerTests: XCTestCase { + + func testInit() { + + // Our Fake info + let fakeDict : [String:Any] = [ + "id" : "fake_id", + "login" : "fakelogin", + "avatar_url" : "http://fakeavatar.com/avatar.png" + ] + + // Apply it + let fakeObject = Owner(data: fakeDict) + + // Assert + XCTAssert(fakeObject.id != "", "O campo \"id\" não foi preenchido.") + XCTAssert(fakeObject.username != "", "O campo \"username\" não foi preenchido.") + XCTAssert(fakeObject.name != "", "O campo \"name\" não foi preenchido.") + XCTAssert(fakeObject.picture != "", "O campo \"picture\" não foi preenchido.") + } +} + diff --git a/DesafioIOS/DesafioIOSTests/PullRequestTests.swift b/DesafioIOS/DesafioIOSTests/PullRequestTests.swift new file mode 100644 index 0000000..15b1e4c --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/PullRequestTests.swift @@ -0,0 +1,65 @@ +// +// PullRequestTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class PullRequestTests: XCTestCase { + + fileprivate let timeOutInterval : TimeInterval = 10.0 + + func testInit() { + + // Our Fake info + let fakeDict : [String:Any] = [ + "id" : 12345, + "title" : "Fake Title", + "body" : "Lorem Ipsum Dolor sit amen", + "html_url" : "http://google.com", + "state" : "open", + "user" : [ + "id" : "fake_id", + "login" : "fakelogin", + "avatar_url" : "http://fakeavatar.com/avatar.png" + ] + ] + + // Apply it + let fakeObject = PullRequest(data: fakeDict) + + // Assert + XCTAssert(fakeObject.id != 0, "O campo \"id\" não foi preenchido.") + XCTAssert(fakeObject.title != "", "O campo \"title\" não foi preenchido.") + XCTAssert(fakeObject.objectDescription != "", "O campo \"objectDescription\" não foi preenchido.") + XCTAssert(fakeObject.htmlUrl != "", "O campo \"htmlUrl\" não foi preenchido.") + XCTAssert(fakeObject.state != "", "O campo \"state\" não foi preenchido.") + // Assert Owner + XCTAssertNotNil(fakeObject.owner, "O campo \"owner\" não foi preenchido.") + } + + func testLoad() { + + // Expectation + let expectation = self.expectation(description: "Esperando resultado de Pull Requests") + + // Launch + let testOwner = "elastic" + let testRepository = "elasticsearch" + PullRequest.load(owner: testOwner, repository: testRepository, succeed: { (result) in + XCTAssert(result.count > 0, "Não houveram resultados.") + expectation.fulfill() + }) { (error) in + XCTAssert(error != "", "Descrição do erro falhou.") + expectation.fulfill() + } + + // Assert + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } +} + diff --git a/DesafioIOS/DesafioIOSTests/PullRequestsViewControllerTests.swift b/DesafioIOS/DesafioIOSTests/PullRequestsViewControllerTests.swift new file mode 100644 index 0000000..506517a --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/PullRequestsViewControllerTests.swift @@ -0,0 +1,205 @@ +// +// PullRequestsViewControllerTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class PullRequestsViewControllerTests: XCTestCase { + + fileprivate var vc : MockPullRequestsViewController! = nil + fileprivate let timeOutInterval : TimeInterval = 10.0 + fileprivate let fakeDict : [String:Any] = [ + "id" : 12345, + "name" : "elasticsearch", + "full_name" : "elastic/elasticsearch", + "description" : "Lorem ipsum dolor sit amen", + "forks_count" : 999, + "stargazers_count" : 999, + "owner" : [ + "id" : "fake_id", + "login" : "elastic", + "avatar_url" : "http://fakeavatar.com/avatar.png" + ] + ] + + // MARK: - Lifecycle Methods + override func setUp() { + super.setUp() + self.vc = MockPullRequestsViewController() + } + + override func tearDown() { + self.vc = nil + super.tearDown() + } + + // MARK: - Tests + func testSetup() { + + // Apply it + let fakeObject = Repository(data: fakeDict) + self.vc.repository = fakeObject + + // Init view + var view : UIView? = self.vc.view + + // Asserting proprieties + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssertNotNil(self.vc.tableView, "UITableView não existe.") + XCTAssertNotNil(self.vc.repository, "Esta tela requere um objeto do tipo \"Repository\" para continuar.") + XCTAssert(self.vc.title == self.vc.repository?.name, "O título deve ser o nome do repositório.") + XCTAssert(self.vc.navigationItem.title == self.vc.repository?.name, "O título deve ser o nome do repositório.") + XCTAssert(self.vc.tableView.rowHeight == UITableViewAutomaticDimension, "A altura das células da UITableView não é dinâmica.") + XCTAssert(self.vc.tableView.estimatedRowHeight == 140, "A altura das células da UITableView não é 130px.") + XCTAssertNotNil(self.vc.tableView.refreshControl, "UITableView não possui UIRefreshControl.") + + // Release View + view = nil + } + + func testAddObservers() { + + // Try to Add observers + self.vc.addObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller não possui Observers.") + XCTAssert(self.vc.hasObservers == true, "Não foram adicionados observers neste View Controller") + + // Try to Add observers + self.vc.removeObservers() + } + + func testRemoveObservers() { + + // Try to Add observers + self.vc.addObservers() + // Try to Add observers + self.vc.removeObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller possui Observers.") + XCTAssert(self.vc.hasObservers == false, "Este View Controller possui Observers") + } + + func testNotificationIsReachable() { + + // Try to Add observers + self.vc.addObservers() + + // Launch Notification + NotificationCenter.default.post(name: NotificationCenter.Name.Reachable, object: nil) + + // Test proprieties + XCTAssertNotNil(self.vc.notificationReceived, "Este View Controller não recebeu notificações.") + XCTAssert(self.vc.notificationReceived == NotificationCenter.Name.Reachable.rawValue, "Este View Controller não recebeu a notificação \"\(NotificationCenter.Name.Reachable.rawValue)\".") + + // Try to Add observers + self.vc.removeObservers() + } + + func testNotificationNotReachable() { + + // Try to Add observers + self.vc.addObservers() + + // Launch Notification + NotificationCenter.default.post(name: NotificationCenter.Name.NotReachable, object: nil) + + // Test proprieties + XCTAssertNotNil(self.vc.notificationReceived, "Este View Controller não recebeu notificações.") + XCTAssert(self.vc.notificationReceived == NotificationCenter.Name.NotReachable.rawValue, "Este View Controller não recebeu a notificação \"\(NotificationCenter.Name.NotReachable.rawValue)\".") + + // Try to Add observers + self.vc.removeObservers() + } + + func testRefresh() { + + // Launch + self.vc.refresh() + + // Assert + XCTAssertNotNil(self.vc.didRefreshPage, "Os dados não foram recarregados.") + } + + func testTriggerRefreshControl() { + + // Init view + var view : UIView? = self.vc.view + + // Trigger + self.vc.triggerRefreshControl() + + // Assert + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssert(self.vc.refreshControl!.isRefreshing, "UIRefreshControl não foi ativado.") + XCTAssert(self.vc.tableView.contentOffset.y != 0, "Content offset da UITableView não está diferente.") + + // Release view + view = nil + } + + func testFetchData() { + + // Apply it + let fakeObject = Repository(data: fakeDict) + self.vc.repository = fakeObject + + // Expectation + let expectation = self.expectation(description: "Esperando dados de de Pull Requests") + + // Launch + let previousSourceCount = self.vc.source.count + self.vc.fetchData { [weak self] in + if let this = self { + // Assert + XCTAssert(previousSourceCount < this.vc.source.count, "O conteúdo da coleção de dados é menor ou igual à conferência anterior.") + } + expectation.fulfill() + } + + // Assert + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } +} + +// MARK: - Mock VC +class MockPullRequestsViewController : PullRequestsViewController { + + var notificationReceived : String? = nil + var hasObservers : Bool? = nil + var didRefreshPage : Bool? = nil + + override func addObservers() { + super.addObservers() + self.hasObservers = true + } + + override func removeObservers() { + super.removeObservers() + self.hasObservers = false + } + + override func notificationIsReachable(n: Notification) { + super.notificationIsReachable(n: n) + self.notificationReceived = n.name.rawValue + } + + override func notificationNotReachable(n: Notification) { + super.notificationNotReachable(n: n) + self.notificationReceived = n.name.rawValue + } + + override func refresh() { + super.refresh() + self.didRefreshPage = true + } +} + + diff --git a/DesafioIOS/DesafioIOSTests/ReachabilityManagerTests.swift b/DesafioIOS/DesafioIOSTests/ReachabilityManagerTests.swift new file mode 100644 index 0000000..f4b1416 --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/ReachabilityManagerTests.swift @@ -0,0 +1,41 @@ +// +// ReachabilityManagerTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class ReachabilityManagerTests: XCTestCase { + + func testSubscribe() { + + // Subscribe + ReachabilityManager.subscribe() + + // Assert + XCTAssert(ReachabilityManager.isSubscribed, "ReachabilityManager não está recebendo eventos.") + + // Unsubscribe + ReachabilityManager.unsubscribe() + } + + func testUnsubscribe() { + + // Subscribe + ReachabilityManager.subscribe() + + // Assert + XCTAssert(ReachabilityManager.isSubscribed, "ReachabilityManager não está recebendo eventos.") + + // Unsubscribe + ReachabilityManager.unsubscribe() + + // Assert + XCTAssertFalse(ReachabilityManager.isSubscribed, "ReachabilityManager está recebendo eventos.") + } +} + diff --git a/DesafioIOS/DesafioIOSTests/RepositoriesViewControllerTests.swift b/DesafioIOS/DesafioIOSTests/RepositoriesViewControllerTests.swift new file mode 100644 index 0000000..5082c0d --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/RepositoriesViewControllerTests.swift @@ -0,0 +1,216 @@ +// +// RepositoriesViewControllerTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class RepositoriesViewControllerTests: XCTestCase { + + fileprivate var vc : MockRepositoriesViewController! = nil + fileprivate let timeOutInterval : TimeInterval = 5.0 + + // MARK: - Lifecycle Methods + override func setUp() { + super.setUp() + self.vc = MockRepositoriesViewController() + } + + override func tearDown() { + self.vc = nil + super.tearDown() + } + + // MARK: - Tests + func testSetup() { + + // Init view + var view : UIView? = self.vc.view + + // Asserting proprieties + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssertNotNil(self.vc.tableView, "UITableView não existe.") + XCTAssert(self.vc.tableView.rowHeight == UITableViewAutomaticDimension, "A altura das células da UITableView não é dinâmica.") + XCTAssert(self.vc.tableView.estimatedRowHeight == 130, "A altura das células da UITableView não é 130px.") + XCTAssertNotNil(self.vc.tableView.refreshControl, "UITableView não possui UIRefreshControl.") + + // Release View + view = nil + } + + func testAddObservers() { + + // Try to Add observers + self.vc.addObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller não possui Observers.") + XCTAssert(self.vc.hasObservers == true, "Não foram adicionados observers neste View Controller") + + // Try to Add observers + self.vc.removeObservers() + } + + func testRemoveObservers() { + + // Try to Add observers + self.vc.addObservers() + // Try to Add observers + self.vc.removeObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller possui Observers.") + XCTAssert(self.vc.hasObservers == false, "Este View Controller possui Observers") + } + + func testNotificationIsReachable() { + + // Try to Add observers + self.vc.addObservers() + + // Launch Notification + NotificationCenter.default.post(name: NotificationCenter.Name.Reachable, object: nil) + + // Test proprieties + XCTAssertNotNil(self.vc.notificationReceived, "Este View Controller não recebeu notificações.") + XCTAssert(self.vc.notificationReceived == NotificationCenter.Name.Reachable.rawValue, "Este View Controller não recebeu a notificação \"\(NotificationCenter.Name.Reachable.rawValue)\".") + + // Try to Add observers + self.vc.removeObservers() + } + + func testNotificationNotReachable() { + + // Try to Add observers + self.vc.addObservers() + + // Launch Notification + NotificationCenter.default.post(name: NotificationCenter.Name.NotReachable, object: nil) + + // Test proprieties + XCTAssertNotNil(self.vc.notificationReceived, "Este View Controller não recebeu notificações.") + XCTAssert(self.vc.notificationReceived == NotificationCenter.Name.NotReachable.rawValue, "Este View Controller não recebeu a notificação \"\(NotificationCenter.Name.NotReachable.rawValue)\".") + + // Try to Add observers + self.vc.removeObservers() + } + + func testRefresh() { + + // Expectation + let expectation = self.expectation(description: "Carregar dados") + + // Fetch Data to fill source + self.vc.fetchData { [weak self] in + if let this = self { + // Assert + XCTAssert(this.vc.source.count > 0, "A coleção de dados está vazia.") + // Fetch second page + this.vc.triggerInfiniteScrolling { [unowned this] in + // Assert + XCTAssert(this.vc.page > 1, "A página ainda é a primeira.") + expectation.fulfill() + // Try to refresh + this.vc.refresh() + // Assert + XCTAssert(this.vc.page == 1, "A página ativa não é a primeira") + } + } + } + + // Wait + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } + + func testTriggerRefreshControl() { + + // Init view + var view : UIView? = self.vc.view + + // Trigger + self.vc.triggerRefreshControl() + + // Assert + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssert(self.vc.refreshControl!.isRefreshing, "UIRefreshControl não foi ativado.") + XCTAssert(self.vc.tableView.contentOffset.y != 0, "Content offset da UITableView não está diferente.") + + // Release view + view = nil + } + + func testFetchData() { + + // Expectation + let expectation = self.expectation(description: "Esperando dados da primeira página de Repositories") + + // Launch + let previousSourceCount = self.vc.source.count + self.vc.fetchData { [weak self] in + if let this = self { + // Assert + XCTAssert(previousSourceCount < this.vc.source.count, "O conteúdo da coleção de dados é menor ou igual à conferência anterior.") + } + expectation.fulfill() + } + + // Assert + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } + + func testTriggerInfiniteScrolling() { + + // Fetch first page + self.vc.fetchData { [weak self] in + if let this = self { + // Expectation + let expectation = this.expectation(description: "Esperando dados da próxima página de Repositories") + + // Launch + let previousSourceCount = this.vc.source.count + this.vc.triggerInfiniteScrolling { [unowned this] in + // Assert + XCTAssert(previousSourceCount < this.vc.source.count, "O conteúdo da coleção de dados é menor ou igual à conferência anterior.") + XCTAssert(this.vc.page > 1, "A página continua sendo a primeira.") + expectation.fulfill() + } + + // Assert + this.waitForExpectations(timeout: this.timeOutInterval, handler: nil) + } + } + } +} + +// MARK: - Mock VC +class MockRepositoriesViewController : RepositoriesViewController { + + var notificationReceived : String? = nil + var hasObservers : Bool? = nil + + override func addObservers() { + super.addObservers() + self.hasObservers = true + } + + override func removeObservers() { + super.removeObservers() + self.hasObservers = false + } + + override func notificationIsReachable(n: Notification) { + super.notificationIsReachable(n: n) + self.notificationReceived = n.name.rawValue + } + + override func notificationNotReachable(n: Notification) { + super.notificationNotReachable(n: n) + self.notificationReceived = n.name.rawValue + } +} + + diff --git a/DesafioIOS/DesafioIOSTests/RepositoryTests.swift b/DesafioIOS/DesafioIOSTests/RepositoryTests.swift new file mode 100644 index 0000000..01f090f --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/RepositoryTests.swift @@ -0,0 +1,65 @@ +// +// RepositoryTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class RepositoryTests: XCTestCase { + + fileprivate let timeOutInterval : TimeInterval = 10.0 + + func testInit() { + + // Our Fake info + let fakeDict : [String:Any] = [ + "id" : 12345, + "name" : "Fake Name", + "full_name" : "Fake Name/Full Name", + "description" : "Lorem ipsum dolor sit amen", + "forks_count" : 999, + "stargazers_count" : 999, + "owner" : [ + "id" : "fake_id", + "login" : "fakelogin", + "avatar_url" : "http://fakeavatar.com/avatar.png" + ] + ] + + // Apply it + let fakeObject = Repository(data: fakeDict) + + // Assert + XCTAssert(fakeObject.id != 0, "O campo \"id\" não foi preenchido.") + XCTAssert(fakeObject.name != "", "O campo \"name\" não foi preenchido.") + XCTAssert(fakeObject.objectDescription != "", "O campo \"objectDescription\" não foi preenchido.") + XCTAssert(fakeObject.forks != 0, "O campo \"forks\" não foi preenchido.") + XCTAssert(fakeObject.stars != 0, "O campo \"stars\" não foi preenchido.") + // Assert Owner + XCTAssertNotNil(fakeObject.owner, "O campo \"owner\" não foi preenchido.") + } + + func testLoad() { + + // Expectation + let expectation = self.expectation(description: "Esperando resultado de Repositories") + + // Launch + Repository.load(page: 1, succeed: { (result) in + XCTAssert(result.count > 0, "Não houveram resultados.") + expectation.fulfill() + }) { (error) in + XCTAssert(error != "", "Descrição do erro falhou.") + expectation.fulfill() + } + + // Assert + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } +} + + diff --git a/DesafioIOS/DesafioIOSTests/RestClientTests.swift b/DesafioIOS/DesafioIOSTests/RestClientTests.swift new file mode 100644 index 0000000..0422c39 --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/RestClientTests.swift @@ -0,0 +1,34 @@ +// +// RestClientTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class RestClientTests: XCTestCase { + + fileprivate let timeOutInterval : TimeInterval = 10.0 + + func testSendRequest() { + + // Expectation + let expectation = self.expectation(description: "Esperando resultado de Rest Client (pull requests)") + + // Launch + RestClient.sendRequest(url: "https://api.github.com/repos/elastic/elasticsearch/pulls") { (succeed, result) in + XCTAssert(succeed, "A requisição falhou.") + XCTAssertNotNil(result, "A resposta é nula.") + if let safeResult = result { + XCTAssert(safeResult is [[String:Any]], "A resposta não é uma coleção de dados.") + } + expectation.fulfill() + } + + // Assert + self.waitForExpectations(timeout: self.timeOutInterval, handler: nil) + } +} diff --git a/DesafioIOS/DesafioIOSTests/WebViewControllerTests.swift b/DesafioIOS/DesafioIOSTests/WebViewControllerTests.swift new file mode 100644 index 0000000..ba350c0 --- /dev/null +++ b/DesafioIOS/DesafioIOSTests/WebViewControllerTests.swift @@ -0,0 +1,134 @@ +// +// WebViewControllerTests.swift +// DesafioIOSTests +// +// Created by Felipe Ricieri on 28/10/17. +// Copyright © 2017 Nexaas. All rights reserved. +// + +import XCTest +@testable import DesafioIOS + +class WebViewControllerTests: XCTestCase { + + fileprivate var vc : MockWebViewController! = nil + fileprivate let timeOutInterval : TimeInterval = 10.0 + fileprivate let fakeDict : [String:Any] = [ + "id" : 12345, + "title" : "Fake Title", + "body" : "Lorem Ipsum Dolor sit amen", + "html_url" : "http://google.com", + "user" : [ + "id" : "fake_id", + "login" : "fakelogin", + "avatar_url" : "http://fakeavatar.com/avatar.png" + ] + ] + + // MARK: - Lifecycle Methods + override func setUp() { + super.setUp() + self.vc = MockWebViewController() + } + + override func tearDown() { + self.vc = nil + super.tearDown() + } + + // MARK: - Tests + func testSetup() { + + // Apply it + let fakeObject = PullRequest(data: fakeDict) + self.vc.pullRequest = fakeObject + + // Init view + var view : UIView? = self.vc.view + + // Asserting proprieties + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssertNotNil(self.vc.pullRequest, "Esta tela requere um objeto do tipo \"Pull Request\" para continuar.") + XCTAssert(self.vc.title == "", "O título deve ser o nome do pull request.") + XCTAssert(self.vc.navigationItem.title == "", "O título deve ser o nome do repositório.") + + // Release View + view = nil + } + + func testLaunchUrl() { + + // Apply it + let fakeObject = PullRequest(data: fakeDict) + self.vc.pullRequest = fakeObject + + // Init view + var view : UIView? = self.vc.view + + // Fire + self.vc.launchUrl() + + // Assert + XCTAssertNotNil(view, "Este View Controller não possui UIView.") + XCTAssertNotNil(self.vc.url, "A URL ainda não foi carregada.") + XCTAssert(self.vc.title == self.vc.pullRequest?.title, "O título deve ser o nome do pull request.") + XCTAssert(self.vc.navigationItem.title == self.vc.pullRequest?.title, "O título deve ser o nome do repositório.") + + // Release View + view = nil + } + + func testAddObservers() { + + // Try to Add observers + self.vc.addObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller não possui Observers.") + XCTAssert(self.vc.hasObservers == true, "Não foram adicionados observers neste View Controller") + + // Try to Add observers + self.vc.removeObservers() + } + + func testRemoveObservers() { + + // Try to Add observers + self.vc.addObservers() + // Try to Add observers + self.vc.removeObservers() + + // Test proprieties + XCTAssertNotNil(self.vc.hasObservers, "Este View Controller possui Observers.") + XCTAssert(self.vc.hasObservers == false, "Este View Controller possui Observers") + } +} + +// MARK: - Mock VC +class MockWebViewController : WebViewController { + + var notificationReceived : String? = nil + var hasObservers : Bool? = nil + + override func addObservers() { + super.addObservers() + self.hasObservers = true + } + + override func removeObservers() { + super.removeObservers() + self.hasObservers = false + } + + override func notificationIsReachable(n: Notification) { + super.notificationIsReachable(n: n) + self.notificationReceived = n.name.rawValue + } + + override func notificationNotReachable(n: Notification) { + super.notificationNotReachable(n: n) + self.notificationReceived = n.name.rawValue + } +} + + From 4f3fc0eb61f051a1e19beff47aa1ec7f2b3153c5 Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:30:08 -0200 Subject: [PATCH 10/37] LaunchScreen --- .../Base.lproj/LaunchScreen.storyboard | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/DesafioIOS/DesafioIOS/Base.lproj/LaunchScreen.storyboard b/DesafioIOS/DesafioIOS/Base.lproj/LaunchScreen.storyboard index f83f6fd..193e94d 100644 --- a/DesafioIOS/DesafioIOS/Base.lproj/LaunchScreen.storyboard +++ b/DesafioIOS/DesafioIOS/Base.lproj/LaunchScreen.storyboard @@ -1,23 +1,50 @@ - - + + + + + - + - + - - + + + + + + - - + + + + + + + + + + + + - + From 1d1cdfe68651da326dea8ec1767efdc3f60844ed Mon Sep 17 00:00:00 2001 From: Felipe Ricieri Date: Sat, 28 Oct 2017 11:33:35 -0200 Subject: [PATCH 11/37] Main Storyboard set --- .../DesafioIOS/Base.lproj/Main.storyboard | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard b/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard index 7651c0a..ceca22c 100644 --- a/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard +++ b/DesafioIOS/DesafioIOS/Base.lproj/Main.storyboard @@ -5,6 +5,7 @@ + @@ -54,13 +55,18 @@ - + - + + + + + + - + - + + + + + +