From 86546277d88936399c8b911f8ef7d1a682fa9631 Mon Sep 17 00:00:00 2001 From: Yazan Halawa Date: Thu, 15 Sep 2022 17:27:38 -0700 Subject: [PATCH] Push Submission --- Practical Example/.DS_Store | Bin 0 -> 6148 bytes .../.DS_Store | Bin 0 -> 8196 bytes .../project.pbxproj | 589 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../UserInterfaceState.xcuserstate | Bin 0 -> 91355 bytes .../xcdebugger/Expressions.xcexplist | 17 + ...Raywenderlich Interview take home.xcscheme | 100 +++ .../xcdebugger/Breakpoints_v2.xcbkptlist | 40 ++ .../xcschemes/xcschememanagement.plist | 32 + .../.DS_Store | Bin 0 -> 8196 bytes .../AppDelegate.swift | 79 +++ .../Assets.xcassets/.DS_Store | Bin 0 -> 6148 bytes .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../placeholder.imageset/Contents.json | 15 + .../istockphoto-1147544807-612x612.jpg | Bin 0 -> 7552 bytes .../Base.lproj/LaunchScreen.storyboard | 25 + .../Core/Extensions/UIImageExtensions.swift | 48 ++ .../Info.plist | 23 + .../Logging/Logging.swift | 17 + .../Models/Resource.swift | 51 ++ .../Networking/Endpoint.swift | 29 + .../Networking/Endpoints/RayWenderlich.swift | 38 ++ .../Networking/NetworkManager.swift | 62 ++ .../Networking/ReachabilityMonitor.swift | 45 ++ .../.xccurrentversion | 8 + .../contents | 4 + .../ResourceList/.DS_Store | Bin 0 -> 6148 bytes .../ResourceListViewController.swift | 115 ++++ .../ResourceList/ResourceListViewModel.swift | 67 ++ .../ResourceList/ResourceView.swift | 20 + .../ResourceList/ResourceView.xib | 67 ++ .../SceneDelegate.swift | 63 ++ ...ywenderlich_Interview_take_homeTests.swift | 59 ++ 36 files changed, 1658 insertions(+) create mode 100644 Practical Example/.DS_Store create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/.DS_Store create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.pbxproj create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcuserdata/Yazan.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcuserdata/Yazan.xcuserdatad/xcdebugger/Expressions.xcexplist create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcshareddata/xcschemes/Raywenderlich Interview take home.xcscheme create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/.DS_Store create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/AppDelegate.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/.DS_Store create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/Contents.json create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/placeholder.imageset/Contents.json create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/placeholder.imageset/istockphoto-1147544807-612x612.jpg create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Base.lproj/LaunchScreen.storyboard create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Core/Extensions/UIImageExtensions.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Info.plist create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Logging/Logging.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Models/Resource.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoint.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoints/RayWenderlich.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/NetworkManager.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/ReachabilityMonitor.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/.xccurrentversion create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/Raywenderlich_Interview_take_home.xcdatamodel/contents create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/.DS_Store create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewController.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewModel.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.xib create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/SceneDelegate.swift create mode 100644 Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take homeTests/Raywenderlich_Interview_take_homeTests.swift diff --git a/Practical Example/.DS_Store b/Practical Example/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3cb3f13a6c0836039f9497f2244be45186fcf8db GIT binary patch literal 6148 zcmeHK!A=4(5Pg-^5WV2d9PbiDO&;p(bIl{ALTFjHC}wvHc^7! z0F9X;Gp{?H>11EFEd#*xmt_y=0BBPMlT(^+MCL`e?SWl@}57P)nHy*SC3gqz8;mGG7u?kpY3#2T}&BJ2L1yBaz3O~!7O4E&>tN%z6BsA zbbH}gdI`x%BW4ksfSjS2kV*`xCbk$Rq|=|qxGZ85Fr>r8=EKCwCUz*sSLgbv4~NMD zs#OM*flUTl;jtt4|Mk!P|7MatDFe#Dzhb~7`5^D}mEzsH^>T98rqp|?h{QDkeF{yy hj{QKc;tQ%5&ZROSW)Yi!^icGVfTux?GH_4^J^)E*m<|8{ literal 0 HcmV?d00001 diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/.DS_Store b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..bafc90dbdbbd3529a21f882f43b5bc22ec91821e GIT binary patch literal 8196 zcmeHMJ5B>J5PgOekr0S7<{^3v=nrRZ~zF2lE7{pB_V-sD>_6=#VI%fC!s^) z07x`Ee+0WqQn8B=%t)SR*)z8MCiZ$90IG4>t^+jyRThJrYg7xw^qVp#E52o&sOS%L zFhHAngm}(c8iWC1Ko}4PgaKjTH!#3&wvpRX)W~&Yk^%F(-x|Q-{tS%DF@NnU&8_l%1V^ zfi;H;9f}qPgn@YmOzysgBb?$Ivt0h(#~FIug$K;(CpM>_qEDQ$Z9@AC)&p8-BR#$VQ~BIdXCE*9sF15n(_WAOow8g__C#_m{u_3s=H` zFz|;NP?e||HMl@?wOl0T8XK^juoy9RsYB~RWpBDfn~oDd|1e}dpsDc*J*E!XLXn36 MOM@t3;71ww03fdqr2qf` literal 0 HcmV?d00001 diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.pbxproj b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.pbxproj new file mode 100644 index 0000000..20c2eab --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.pbxproj @@ -0,0 +1,589 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + AC9A457A28D125B10032DD02 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A457928D125B10032DD02 /* Resource.swift */; }; + AC9A457D28D129980032DD02 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A457C28D129980032DD02 /* Endpoint.swift */; }; + AC9A457F28D12C350032DD02 /* RayWenderlich.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A457E28D12C350032DD02 /* RayWenderlich.swift */; }; + AC9A458228D130660032DD02 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A458128D130660032DD02 /* NetworkManager.swift */; }; + AC9A458528D13B500032DD02 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A458428D13B500032DD02 /* Logging.swift */; }; + AC9A458728D140630032DD02 /* ReachabilityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A458628D140630032DD02 /* ReachabilityMonitor.swift */; }; + AC9A458A28D14A180032DD02 /* ResourceListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A458928D14A180032DD02 /* ResourceListViewModel.swift */; }; + AC9A458C28D192790032DD02 /* ResourceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A458B28D192790032DD02 /* ResourceView.swift */; }; + AC9A458E28D193350032DD02 /* ResourceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AC9A458D28D193350032DD02 /* ResourceView.xib */; }; + AC9A459228D3BBAB0032DD02 /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9A459128D3BBAB0032DD02 /* UIImageExtensions.swift */; }; + ACBDD07728D1201B0075927E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBDD07628D1201B0075927E /* AppDelegate.swift */; }; + ACBDD07928D1201B0075927E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBDD07828D1201B0075927E /* SceneDelegate.swift */; }; + ACBDD07B28D1201B0075927E /* ResourceListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBDD07A28D1201B0075927E /* ResourceListViewController.swift */; }; + ACBDD08128D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ACBDD07F28D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodeld */; }; + ACBDD08328D1201C0075927E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ACBDD08228D1201C0075927E /* Assets.xcassets */; }; + ACBDD08628D1201C0075927E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ACBDD08428D1201C0075927E /* LaunchScreen.storyboard */; }; + ACBDD09128D1201C0075927E /* Raywenderlich_Interview_take_homeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBDD09028D1201C0075927E /* Raywenderlich_Interview_take_homeTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + ACBDD08D28D1201C0075927E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = ACBDD06B28D1201A0075927E /* Project object */; + proxyType = 1; + remoteGlobalIDString = ACBDD07228D1201B0075927E; + remoteInfo = "Raywenderlich Interview take home"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + AC9A457928D125B10032DD02 /* Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = ""; }; + AC9A457C28D129980032DD02 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; + AC9A457E28D12C350032DD02 /* RayWenderlich.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RayWenderlich.swift; sourceTree = ""; }; + AC9A458128D130660032DD02 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; + AC9A458428D13B500032DD02 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + AC9A458628D140630032DD02 /* ReachabilityMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityMonitor.swift; sourceTree = ""; }; + AC9A458928D14A180032DD02 /* ResourceListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceListViewModel.swift; sourceTree = ""; }; + AC9A458B28D192790032DD02 /* ResourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceView.swift; sourceTree = ""; }; + AC9A458D28D193350032DD02 /* ResourceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ResourceView.xib; sourceTree = ""; }; + AC9A459128D3BBAB0032DD02 /* UIImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; + ACBDD07328D1201B0075927E /* Raywenderlich Interview take home.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Raywenderlich Interview take home.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + ACBDD07628D1201B0075927E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + ACBDD07828D1201B0075927E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + ACBDD07A28D1201B0075927E /* ResourceListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceListViewController.swift; sourceTree = ""; }; + ACBDD08028D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Raywenderlich_Interview_take_home.xcdatamodel; sourceTree = ""; }; + ACBDD08228D1201C0075927E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + ACBDD08528D1201C0075927E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + ACBDD08728D1201C0075927E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + ACBDD08C28D1201C0075927E /* Raywenderlich Interview take homeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Raywenderlich Interview take homeTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + ACBDD09028D1201C0075927E /* Raywenderlich_Interview_take_homeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Raywenderlich_Interview_take_homeTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + ACBDD07028D1201B0075927E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ACBDD08928D1201C0075927E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + AC9A457828D1258E0032DD02 /* Models */ = { + isa = PBXGroup; + children = ( + AC9A457928D125B10032DD02 /* Resource.swift */, + ); + path = Models; + sourceTree = ""; + }; + AC9A457B28D129880032DD02 /* Networking */ = { + isa = PBXGroup; + children = ( + AC9A458028D12DB70032DD02 /* Endpoints */, + AC9A457C28D129980032DD02 /* Endpoint.swift */, + AC9A458128D130660032DD02 /* NetworkManager.swift */, + AC9A458628D140630032DD02 /* ReachabilityMonitor.swift */, + ); + path = Networking; + sourceTree = ""; + }; + AC9A458028D12DB70032DD02 /* Endpoints */ = { + isa = PBXGroup; + children = ( + AC9A457E28D12C350032DD02 /* RayWenderlich.swift */, + ); + path = Endpoints; + sourceTree = ""; + }; + AC9A458328D13B480032DD02 /* Logging */ = { + isa = PBXGroup; + children = ( + AC9A458428D13B500032DD02 /* Logging.swift */, + ); + path = Logging; + sourceTree = ""; + }; + AC9A458828D1489B0032DD02 /* ResourceList */ = { + isa = PBXGroup; + children = ( + ACBDD07A28D1201B0075927E /* ResourceListViewController.swift */, + AC9A458928D14A180032DD02 /* ResourceListViewModel.swift */, + AC9A458B28D192790032DD02 /* ResourceView.swift */, + AC9A458D28D193350032DD02 /* ResourceView.xib */, + ); + path = ResourceList; + sourceTree = ""; + }; + AC9A458F28D3BB980032DD02 /* Core */ = { + isa = PBXGroup; + children = ( + AC9A459028D3BB9F0032DD02 /* Extensions */, + ); + path = Core; + sourceTree = ""; + }; + AC9A459028D3BB9F0032DD02 /* Extensions */ = { + isa = PBXGroup; + children = ( + AC9A459128D3BBAB0032DD02 /* UIImageExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + ACBDD06A28D1201A0075927E = { + isa = PBXGroup; + children = ( + ACBDD07528D1201B0075927E /* Raywenderlich Interview take home */, + ACBDD08F28D1201C0075927E /* Raywenderlich Interview take homeTests */, + ACBDD07428D1201B0075927E /* Products */, + ); + sourceTree = ""; + }; + ACBDD07428D1201B0075927E /* Products */ = { + isa = PBXGroup; + children = ( + ACBDD07328D1201B0075927E /* Raywenderlich Interview take home.app */, + ACBDD08C28D1201C0075927E /* Raywenderlich Interview take homeTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + ACBDD07528D1201B0075927E /* Raywenderlich Interview take home */ = { + isa = PBXGroup; + children = ( + AC9A458F28D3BB980032DD02 /* Core */, + AC9A458828D1489B0032DD02 /* ResourceList */, + AC9A458328D13B480032DD02 /* Logging */, + AC9A457B28D129880032DD02 /* Networking */, + AC9A457828D1258E0032DD02 /* Models */, + ACBDD07628D1201B0075927E /* AppDelegate.swift */, + ACBDD07828D1201B0075927E /* SceneDelegate.swift */, + ACBDD08228D1201C0075927E /* Assets.xcassets */, + ACBDD08428D1201C0075927E /* LaunchScreen.storyboard */, + ACBDD08728D1201C0075927E /* Info.plist */, + ACBDD07F28D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodeld */, + ); + path = "Raywenderlich Interview take home"; + sourceTree = ""; + }; + ACBDD08F28D1201C0075927E /* Raywenderlich Interview take homeTests */ = { + isa = PBXGroup; + children = ( + ACBDD09028D1201C0075927E /* Raywenderlich_Interview_take_homeTests.swift */, + ); + path = "Raywenderlich Interview take homeTests"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ACBDD07228D1201B0075927E /* Raywenderlich Interview take home */ = { + isa = PBXNativeTarget; + buildConfigurationList = ACBDD0A028D1201C0075927E /* Build configuration list for PBXNativeTarget "Raywenderlich Interview take home" */; + buildPhases = ( + ACBDD06F28D1201B0075927E /* Sources */, + ACBDD07028D1201B0075927E /* Frameworks */, + ACBDD07128D1201B0075927E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Raywenderlich Interview take home"; + productName = "Raywenderlich Interview take home"; + productReference = ACBDD07328D1201B0075927E /* Raywenderlich Interview take home.app */; + productType = "com.apple.product-type.application"; + }; + ACBDD08B28D1201C0075927E /* Raywenderlich Interview take homeTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = ACBDD0A328D1201C0075927E /* Build configuration list for PBXNativeTarget "Raywenderlich Interview take homeTests" */; + buildPhases = ( + ACBDD08828D1201C0075927E /* Sources */, + ACBDD08928D1201C0075927E /* Frameworks */, + ACBDD08A28D1201C0075927E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ACBDD08E28D1201C0075927E /* PBXTargetDependency */, + ); + name = "Raywenderlich Interview take homeTests"; + productName = "Raywenderlich Interview take homeTests"; + productReference = ACBDD08C28D1201C0075927E /* Raywenderlich Interview take homeTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ACBDD06B28D1201A0075927E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1400; + LastUpgradeCheck = 1400; + TargetAttributes = { + ACBDD07228D1201B0075927E = { + CreatedOnToolsVersion = 14.0; + }; + ACBDD08B28D1201C0075927E = { + CreatedOnToolsVersion = 14.0; + TestTargetID = ACBDD07228D1201B0075927E; + }; + }; + }; + buildConfigurationList = ACBDD06E28D1201A0075927E /* Build configuration list for PBXProject "Raywenderlich Interview take home" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ACBDD06A28D1201A0075927E; + productRefGroup = ACBDD07428D1201B0075927E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ACBDD07228D1201B0075927E /* Raywenderlich Interview take home */, + ACBDD08B28D1201C0075927E /* Raywenderlich Interview take homeTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ACBDD07128D1201B0075927E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AC9A458E28D193350032DD02 /* ResourceView.xib in Resources */, + ACBDD08628D1201C0075927E /* LaunchScreen.storyboard in Resources */, + ACBDD08328D1201C0075927E /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ACBDD08A28D1201C0075927E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ACBDD06F28D1201B0075927E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AC9A457F28D12C350032DD02 /* RayWenderlich.swift in Sources */, + ACBDD07B28D1201B0075927E /* ResourceListViewController.swift in Sources */, + ACBDD07728D1201B0075927E /* AppDelegate.swift in Sources */, + AC9A458A28D14A180032DD02 /* ResourceListViewModel.swift in Sources */, + ACBDD07928D1201B0075927E /* SceneDelegate.swift in Sources */, + AC9A459228D3BBAB0032DD02 /* UIImageExtensions.swift in Sources */, + AC9A458228D130660032DD02 /* NetworkManager.swift in Sources */, + ACBDD08128D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodeld in Sources */, + AC9A457D28D129980032DD02 /* Endpoint.swift in Sources */, + AC9A458C28D192790032DD02 /* ResourceView.swift in Sources */, + AC9A458528D13B500032DD02 /* Logging.swift in Sources */, + AC9A457A28D125B10032DD02 /* Resource.swift in Sources */, + AC9A458728D140630032DD02 /* ReachabilityMonitor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ACBDD08828D1201C0075927E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ACBDD09128D1201C0075927E /* Raywenderlich_Interview_take_homeTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + ACBDD08E28D1201C0075927E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = ACBDD07228D1201B0075927E /* Raywenderlich Interview take home */; + targetProxy = ACBDD08D28D1201C0075927E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + ACBDD08428D1201C0075927E /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + ACBDD08528D1201C0075927E /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + ACBDD09E28D1201C0075927E /* 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++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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; + 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 = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + ACBDD09F28D1201C0075927E /* 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++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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; + 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 = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + ACBDD0A128D1201C0075927E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 486TSQPHB9; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Raywenderlich Interview take home/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.yazanhalawa.Raywenderlich-Interview-take-home"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ACBDD0A228D1201C0075927E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 486TSQPHB9; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Raywenderlich Interview take home/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.yazanhalawa.Raywenderlich-Interview-take-home"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + ACBDD0A428D1201C0075927E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 486TSQPHB9; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.yazanhalawa.Raywenderlich-Interview-take-homeTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Raywenderlich Interview take home.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Raywenderlich Interview take home"; + }; + name = Debug; + }; + ACBDD0A528D1201C0075927E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 486TSQPHB9; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.yazanhalawa.Raywenderlich-Interview-take-homeTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Raywenderlich Interview take home.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Raywenderlich Interview take home"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ACBDD06E28D1201A0075927E /* Build configuration list for PBXProject "Raywenderlich Interview take home" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ACBDD09E28D1201C0075927E /* Debug */, + ACBDD09F28D1201C0075927E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ACBDD0A028D1201C0075927E /* Build configuration list for PBXNativeTarget "Raywenderlich Interview take home" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ACBDD0A128D1201C0075927E /* Debug */, + ACBDD0A228D1201C0075927E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ACBDD0A328D1201C0075927E /* Build configuration list for PBXNativeTarget "Raywenderlich Interview take homeTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ACBDD0A428D1201C0075927E /* Debug */, + ACBDD0A528D1201C0075927E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + ACBDD07F28D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + ACBDD08028D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodel */, + ); + currentVersion = ACBDD08028D1201B0075927E /* Raywenderlich_Interview_take_home.xcdatamodel */; + path = Raywenderlich_Interview_take_home.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ + }; + rootObject = ACBDD06B28D1201A0075927E /* Project object */; +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcuserdata/Yazan.xcuserdatad/UserInterfaceState.xcuserstate b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/project.xcworkspace/xcuserdata/Yazan.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..aeef6b6141f8c46ba0b8211ec21180c927903b46 GIT binary patch literal 91355 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 + + + + + + + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcshareddata/xcschemes/Raywenderlich Interview take home.xcscheme b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcshareddata/xcschemes/Raywenderlich Interview take home.xcscheme new file mode 100644 index 0000000..6c8be35 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcshareddata/xcschemes/Raywenderlich Interview take home.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..bb33d41 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcschemes/xcschememanagement.plist b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..228b541 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home.xcodeproj/xcuserdata/Yazan.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + Raywenderlich Interview take home.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + ACBDD07228D1201B0075927E + + primary + + + ACBDD08B28D1201C0075927E + + primary + + + ACBDD09528D1201C0075927E + + primary + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/.DS_Store b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9be85f68c1172a11e2ae303b4334e48bf47d2cf8 GIT binary patch literal 8196 zcmeHM%We}f6g@6csR{@Ln=a=IDrE!PX&4qr1tj_bX`6|Xv@@M34+^_s!>S9u00{}P zX2X^hp8^u%3lL(#@k5b4;|Uc~D-`U?zRrD&kB^;kuLD4I=4l()0I_6w-NX*__9^;K{=U#;gZ5sp$j3VP-nTzT zTQO>}twAm_7<3}Z}C;K-fD`pYIi7khd1 zZm6((pP3gn;^n?BPW6^*;XaW~(63;d6WhY(;=%hbnoG-WW2oCb<6gxs#~tu*gcA(h z(ZvUjUh!}-l)v6+PHJ8@k|r+?o^?DTo|NmAVd|o3-6k5xUcvJ5yXK?f;~??+cyMgu zvHA|2s#Q7mv2!O;`^Mu}!PWkuae1Fv**8mlU7Rt8=dOOr{rv)`M0=umn~=E?Z{3FI z;vsc2S|)f#U7ucEem=l4`(1hz$l24kbSd@HmM{2EUH;ehFM2$iH}UD!jn{f_UFaNb z{x54xcO4_ritAj*KHpOX=3K2F`@3ARDQ4~(JbAm!*XECYXFv76Ykamn_w)F>xJ!nF zvLXyz(ev1n`Tv9F`~MZ0ZJ0)2ATZ!#K(vy*q(h7FS*eq`cEq~RDvR_hwNxQ&R60(n t(s9bg4~FbVbd@o&k)@W@uw42@K!(&l1H<{R@|(v07sC0kw&}_>zX1fyo?QR{ literal 0 HcmV?d00001 diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/AppDelegate.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/AppDelegate.swift new file mode 100644 index 0000000..b7077d6 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/AppDelegate.swift @@ -0,0 +1,79 @@ +// +// AppDelegate.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import UIKit +import CoreData + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + // 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: "Raywenderlich_Interview_take_home") + 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/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/.DS_Store b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Assets.xcassets/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4a6e9c030a576588342d99547988b0d322bab497 GIT binary patch literal 6148 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 rHTk + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Core/Extensions/UIImageExtensions.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Core/Extensions/UIImageExtensions.swift new file mode 100644 index 0000000..f7b8387 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Core/Extensions/UIImageExtensions.swift @@ -0,0 +1,48 @@ +// +// UIImageExtensions.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/15/22. +// + +import Foundation +import UIKit +import OSLog + +// We want to have a cache of images to optimize performance and reduce unnecessary network calls +let imageCache = NSCache() + +extension UIImageView { + + /// Loads an image from a url and saves it in cache + /// If the image is already in cache it returns it directly, otherwise fetches it from network + /// - Parameter url: the url of the image + func load(fromURL urlStr: String) { + // Check if image is in cache and retrieve it + if let cachedImage = imageCache.object(forKey: urlStr as NSString) { + image = cachedImage + return + } + + // Otherwise fetch from network + if let url = URL(string: urlStr) { + let dataTask = URLSession.shared.dataTask(with: URLRequest(url: url)) { [weak self] data, response, error in + guard let self = self else { return } + if let error = error { + Logger.networking.error("Failed to retrieve image from url \(urlStr) with error \(error)") + return + } + + guard let data = data else { + Logger.networking.error("Failed to retrieve image from url \(urlStr). Request came back with no data") + return + } + + DispatchQueue.main.async { + self.image = UIImage(data: data) + } + } + dataTask.resume() + } + } +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Info.plist b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Info.plist new file mode 100644 index 0000000..0eb786d --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Info.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Logging/Logging.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Logging/Logging.swift new file mode 100644 index 0000000..551e20e --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Logging/Logging.swift @@ -0,0 +1,17 @@ +// +// Logging.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import OSLog +import Foundation + +extension Logger { + private static var subsystem = Bundle.main.bundleIdentifier! + + /// Logs the view cycles like viewDidLoad. + static let viewCycle = Logger(subsystem: subsystem, category: "viewcycle") + static let networking = Logger(subsystem: subsystem, category: "networking") +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Models/Resource.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Models/Resource.swift new file mode 100644 index 0000000..27d0091 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Models/Resource.swift @@ -0,0 +1,51 @@ +// +// Article.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation + +struct ResourceData: Decodable { + let data: [Resource] +} + +struct Resource: Decodable { + let id: String + let type: String + let attributes: Attributes + //let links: Links +} + +struct Attributes: Decodable { + let uri: String + let name: String + let description: String + let released_at: String + let free: Bool + let difficulty: Difficulty? + let content_type: ContentType + let duration: Int + let popularity: Float + let technology_triple_string: String + let contributor_string: String + let ordinal: String? + let professional: Bool + let description_plain_text: String + let video_identifier: String? + let parent_name: String? + let card_artwork_url: String +} + +struct Links: Decodable { + let `self`: String +} + +enum Difficulty: String, Decodable { + case beginner, intermediate, advanced +} + +enum ContentType: String, Decodable { + case article, collection +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoint.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoint.swift new file mode 100644 index 0000000..4a0bf03 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoint.swift @@ -0,0 +1,29 @@ +// +// Endpoint.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation + +/// Any endpoint needs to specify the following information. This allows us to house all logic to construct the URL request in the conforming entity. Then a separate entity can perform the actual logic of making the network request +protocol Endpoint { + var httpMethod: HTTPMethod { get } + var scheme: HTTPScheme { get } + var baseURL: String { get } + var path: String { get } + var queryParameters: [URLQueryItem] { get } +} + +enum HTTPMethod: String, Codable { + case get = "GET" + case post = "POST" + case delete = "DELETE" + case patch = "PATCH" + case put = "PUT" +} + +enum HTTPScheme: String, Codable { + case http, https +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoints/RayWenderlich.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoints/RayWenderlich.swift new file mode 100644 index 0000000..1fd34ae --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/Endpoints/RayWenderlich.swift @@ -0,0 +1,38 @@ +// +// Articles.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation + +enum RayWenderlich: Endpoint { + case articles + case videos + + var httpMethod: HTTPMethod { + return .get + } + + var scheme: HTTPScheme { + return .https + } + + var baseURL: String { + return "raw.githubusercontent.com" + } + + var path: String { + switch self { + case .articles: + return "/raywenderlich/ios-interview/master/Practical Example/articles.json" + case .videos: + return "/raywenderlich/ios-interview/master/Practical Example/videos.json" + } + } + + var queryParameters: [URLQueryItem] { + return [] + } +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/NetworkManager.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/NetworkManager.swift new file mode 100644 index 0000000..0ba9c98 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/NetworkManager.swift @@ -0,0 +1,62 @@ +// +// NetworkManager.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation +import OSLog + +protocol MakesNetworkRequests { + func request(endpoint: Endpoint) async -> (Result) +} +enum NetworkError: Error { + // For most apps any network error that happens will be handled the same with a generic error view, but reachability issues are handled specifically with a banner or something letting the user know to check their connection. So I am calling out only those two types of errors + case networkIsNotReachable + case error +} + +final class NetworkManager: MakesNetworkRequests { + static let shared = NetworkManager() + + private init() {} + + private func buildURL(endpoint: Endpoint) -> URLComponents { + var components = URLComponents() + components.scheme = endpoint.scheme.rawValue + components.host = endpoint.baseURL + components.path = endpoint.path + if !endpoint.queryParameters.isEmpty { + components.queryItems = endpoint.queryParameters + } + return components + } + + func request(endpoint: Endpoint) async -> Result { + guard NetworkMonitor.shared.isReachable else { + return .failure(.networkIsNotReachable) + } + let components = buildURL(endpoint: endpoint) + guard let url = components.url else { + Logger.networking.error("Building URL with scheme \(components.scheme ?? "") host \(components.host ?? "") path \(components.path) Failed") + return .failure(.error) + } + + Logger.networking.info("url \(url)") + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = HTTPMethod.get.rawValue + Logger.networking.info("urlRequest \(urlRequest)") + + do { + // process data + let (data, _) = try await URLSession.shared.data(for: urlRequest) + let responseObject = try JSONDecoder().decode(T.self, from: data) + return .success(responseObject) + } catch { + Logger.networking.error("Network Request to \(components.path) failed with \(error)") + return .failure(.error) + } + } +} + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/ReachabilityMonitor.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/ReachabilityMonitor.swift new file mode 100644 index 0000000..6513afc --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Networking/ReachabilityMonitor.swift @@ -0,0 +1,45 @@ +// +// ReachabilityMonitor.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation +import Network + +class NetworkMonitor { + static let shared = NetworkMonitor() + + let monitor = NWPathMonitor() + private var status: NWPath.Status = .requiresConnection + var isReachable: Bool { status == .satisfied } + var isReachableOnCellular: Bool = true + + private init() { + startMonitoring() + } + + func startMonitoring() { + monitor.pathUpdateHandler = { [weak self] path in + self?.status = path.status + self?.isReachableOnCellular = path.isExpensive + + if path.status == .satisfied { + print("We're connected!") + // post connected notification + } else { + print("No connection.") + // post disconnected notification + } + print(path.isExpensive) + } + + let queue = DispatchQueue(label: "NetworkMonitor") + monitor.start(queue: queue) + } + + func stopMonitoring() { + monitor.cancel() + } +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/.xccurrentversion b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..467fca7 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Raywenderlich_Interview_take_home.xcdatamodel + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/Raywenderlich_Interview_take_home.xcdatamodel/contents b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/Raywenderlich_Interview_take_home.xcdatamodel/contents new file mode 100644 index 0000000..50d2514 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/Raywenderlich_Interview_take_home.xcdatamodeld/Raywenderlich_Interview_take_home.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/.DS_Store b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..036000b929693472e0ec2f944b70f4e546559ae0 GIT binary patch literal 6148 zcmeHKOHRWu5Pbt(RIurirB8s=8-yr5K`#KMh>sE_0Ug6MS7bmGnnqU0QxXffa!J)R1Q{^FGG{R}m3@Pa!` z_TS#Gx3_&aOg3Hg>-whIwf&BMN>csyc)0p1XV>q)?K?aitv}gHsKG!m5DWwZ!N89h z;GM0qJU5I!7zhS}flmhXd`N7HmE&lbj}CUZ1R&}&Itz8JB{U{ER*s_~Efld(qJ@f= z7-Hd^PwrPaj)oQv@!~^#<&WY;^6Jc=vOA`}4EzEE?-vFwQUCw| literal 0 HcmV?d00001 diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewController.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewController.swift new file mode 100644 index 0000000..de5a86b --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewController.swift @@ -0,0 +1,115 @@ +// +// ViewController.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import UIKit +import Combine +import OSLog + +class ResourceListViewController: UIViewController, UITableViewDelegate { + let tableView = UITableView() + let stackView = UIStackView() + let buttonsStackView = UIStackView() + let loadingIndicator = UIActivityIndicatorView() + let viewModel = ResourceListViewModel() + + private var subscriptions = Set() + + override func viewDidLoad() { + super.viewDidLoad() + stackView.axis = .vertical + buttonsStackView.axis = .horizontal + pinStackViewToParentView() + + let articlesButton = UIButton(configuration: .bordered()) + articlesButton.setTitle("Articles", for: .normal) + articlesButton.addTarget(self.viewModel, action: #selector(viewModel.showOnlyArticles), for: .touchUpInside) + let videosButton = UIButton(configuration: .bordered()) + videosButton.setTitle("Videos", for: .normal) + videosButton.addTarget(self.viewModel, action: #selector(viewModel.showOnlyVideos), for: .touchUpInside) + let allButton = UIButton(configuration: .bordered()) + allButton.setTitle("Both", for: .normal) + allButton.addTarget(self.viewModel, action: #selector(viewModel.showAllTypes), for: .touchUpInside) + + buttonsStackView.addArrangedSubview(articlesButton) + buttonsStackView.addArrangedSubview(videosButton) + buttonsStackView.addArrangedSubview(allButton) + + stackView.addArrangedSubview(buttonsStackView) + setupTableView() + startInLoadingState() + fetchData() + } + + private func pinStackViewToParentView() { + view.addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true + stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true + stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true + } + + private func setupTableView() { + tableView.register(ResourceView.nib, forCellReuseIdentifier: ResourceView.identifier) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.dataSource = self + tableView.delegate = self + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 250 + stackView.addArrangedSubview(tableView) + tableView.frame = view.bounds + } + + private func startInLoadingState() { + tableView.isHidden = true + view.addSubview(loadingIndicator) + loadingIndicator.isHidden = false + loadingIndicator.color = .black + loadingIndicator.translatesAutoresizingMaskIntoConstraints = false + loadingIndicator.startAnimating() + loadingIndicator.center = self.view.center + } + + private func fetchData() { + subscriptions = [ + viewModel.$resources + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] _ in + guard let self = self else { return } + self.loadingIndicator.isHidden = true + self.tableView.isHidden = false + self.tableView.reloadData() + }) + ] + Task { + await viewModel.fetchResources() + } + } +} + +extension ResourceListViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.resources.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ResourceView.identifier, for: indexPath) as? ResourceView else { + Logger.viewCycle.error("Failed to dequeue reusable cell with identifier (Resource) at indexPath \(indexPath.row),\(indexPath.section)") + return UITableViewCell() + } + if indexPath.row >= 0 && indexPath.row < viewModel.resources.count { + let resource = viewModel.resources[indexPath.row] + cell.nameLabel.text = resource.attributes.name + cell.descriptionLabel.text = resource.attributes.description + // Start image with loading + cell.artworkImageView.image = UIImage(named: "placeholder") + cell.artworkImageView.load(fromURL: resource.attributes.card_artwork_url) + cell.typeLabel.text = resource.attributes.content_type.rawValue + } + return cell + } +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewModel.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewModel.swift new file mode 100644 index 0000000..cb5c4ad --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceListViewModel.swift @@ -0,0 +1,67 @@ +// +// ResourceListViewModel.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation +import Combine + +class ResourceListViewModel: ObservableObject { + @Published private(set) var resources: [Resource] = [] + private(set) var totalResources: [Resource] = [] + private var shownResourceTypes: [ContentType] = [.article, .collection] { + didSet { + resources = totalResources.filter { shownResourceTypes.contains($0.attributes.content_type) + }.sortedByReleaseDate() + } + } + + private let networkManager: MakesNetworkRequests + + init(withNetworkManager networkManager: MakesNetworkRequests = NetworkManager.shared) { + // We dependency inject the network manager so we can mock it for unit testing purposes when needed + self.networkManager = networkManager + } + + func fetchResources() async { + var resources: [Resource] = [] + + // We want to have all network requests fire in parallel and await on all of them + async let articlesFetchResult: Result = networkManager.request(endpoint: RayWenderlich.articles) + async let videosFetchResult: Result = networkManager.request(endpoint: RayWenderlich.videos) + let results = await [articlesFetchResult, videosFetchResult] + + // For now we just add any values that arrived + // TODO Error handling here. Should we show an error if any call fails? or both? + for result in results { + switch result { + case .success(let resourceData): + resources.append(contentsOf: resourceData.data) + case .failure: + return + } + } + self.totalResources.append(contentsOf: resources.sortedByReleaseDate()) + self.showAllTypes() + } + + @objc func showOnlyArticles() { + shownResourceTypes = [.article] + } + + @objc func showOnlyVideos() { + shownResourceTypes = [.collection] + } + + @objc func showAllTypes() { + shownResourceTypes = [.article, .collection] + } +} + +fileprivate extension Array where Element == Resource { + func sortedByReleaseDate() -> [Resource] { + return self.sorted(by: { $0.attributes.released_at.compare($1.attributes.released_at) == .orderedDescending }) + } +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.swift new file mode 100644 index 0000000..05c976d --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.swift @@ -0,0 +1,20 @@ +// +// ResourceView.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import Foundation +import UIKit + +class ResourceView: UITableViewCell { + static let identifier = "ResourceView" + + static let nib = UINib(nibName: identifier, bundle: nil) + + @IBOutlet weak var artworkImageView: UIImageView! + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var typeLabel: UILabel! +} diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.xib b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.xib new file mode 100644 index 0000000..9faf7eb --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/ResourceList/ResourceView.xib @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/SceneDelegate.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/SceneDelegate.swift new file mode 100644 index 0000000..d292be0 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take home/SceneDelegate.swift @@ -0,0 +1,63 @@ +// +// SceneDelegate.swift +// Raywenderlich Interview take home +// +// Created by Yazan Halawa on 9/13/22. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + // If this scene's self.window is nil then set a new UIWindow object to it. + let _ = NetworkMonitor.shared // To start monitoring network reachability + + print("Scene willConnectTo.") + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(frame: windowScene.coordinateSpace.bounds) + window?.windowScene = windowScene + window?.rootViewController = ResourceListViewController() + window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + + // Save changes in the application's managed object context when the application transitions to the background. + (UIApplication.shared.delegate as? AppDelegate)?.saveContext() + } + + +} + diff --git a/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take homeTests/Raywenderlich_Interview_take_homeTests.swift b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take homeTests/Raywenderlich_Interview_take_homeTests.swift new file mode 100644 index 0000000..611b959 --- /dev/null +++ b/Practical Example/Raywenderlich-interview-Yazan-Halawa-submission/Raywenderlich Interview take homeTests/Raywenderlich_Interview_take_homeTests.swift @@ -0,0 +1,59 @@ +// +// Raywenderlich_Interview_take_homeTests.swift +// Raywenderlich Interview take homeTests +// +// Created by Yazan Halawa on 9/13/22. +// + +import XCTest +@testable import Raywenderlich_Interview_take_home + +final class Raywenderlich_Interview_take_homeTests: XCTestCase { + class MockNetworkManager: MakesNetworkRequests { + let resource1 = Resource(id: "1", type: "type1", attributes: Attributes(uri: "randomURI", name: "randomName", description: "randomDesc", released_at: "2020-03-31T13:00:00.000Z", free: true, difficulty: .beginner, content_type: .article, duration: 2, popularity: 2, technology_triple_string: "", contributor_string: "", ordinal: nil, professional: false, description_plain_text: "", video_identifier: "", parent_name: "", card_artwork_url: "")) + let resource2 = Resource(id: "2", type: "type1", attributes: Attributes(uri: "randomURI", name: "randomName", description: "randomDesc", released_at: "2020-03-29T13:00:00.000Z", free: true, difficulty: .beginner, content_type: .collection, duration: 2, popularity: 2, technology_triple_string: "", contributor_string: "", ordinal: nil, professional: false, description_plain_text: "", video_identifier: "", parent_name: "", card_artwork_url: "")) + let resource3 = Resource(id: "3", type: "type1", attributes: Attributes(uri: "randomURI", name: "randomName", description: "randomDesc", released_at: "2020-03-30T13:00:00.000Z", free: true, difficulty: .beginner, content_type: .collection, duration: 2, popularity: 2, technology_triple_string: "", contributor_string: "", ordinal: nil, professional: false, description_plain_text: "", video_identifier: "", parent_name: "", card_artwork_url: "")) + + func request(endpoint: Endpoint) async -> (Result) { + + let data = ResourceData(data: [resource1, resource2, resource3]) + return .success(data as! T) + } + } + + func testFetchingResourcesReturnsMockValues() async throws { + let viewModel = ResourceListViewModel(withNetworkManager: MockNetworkManager()) + await viewModel.fetchResources() + // Since in the view model we fetch resources which makes a call to request twice we will duplicate the three resources resulting in 6 + assert(viewModel.resources.count == 6) + } + + func testFilteringToOnlyArticlesReturnsOnlyArticles() async throws { + let viewModel = ResourceListViewModel(withNetworkManager: MockNetworkManager()) + await viewModel.fetchResources() + viewModel.showOnlyArticles() + // There are 2 "article" type resources in the mocked data + assert(viewModel.resources.count == 2) + } + + func testFilteringToOnlyVideosReturnsOnlyVideos() async throws { + let viewModel = ResourceListViewModel(withNetworkManager: MockNetworkManager()) + await viewModel.fetchResources() + viewModel.showOnlyVideos() + // There are 4 "collection" type resources in the mocked data + assert(viewModel.resources.count == 4) + } + + func testSortingResourcesByReleaseDate() async throws { + let viewModel = ResourceListViewModel(withNetworkManager: MockNetworkManager()) + await viewModel.fetchResources() + // Since we have duplicates this will be a good test if items released on the same data + // Since in our mock resources we have resource 1 released latest, then resource 3, then 2. We should have that order with each resource repeated back to back + assert(viewModel.resources[0].id == "1") + assert(viewModel.resources[1].id == "1") + assert(viewModel.resources[2].id == "3") + assert(viewModel.resources[3].id == "3") + assert(viewModel.resources[4].id == "2") + assert(viewModel.resources[5].id == "2") + } +}