From 8faf05180155d9ba3f6d41ef7a9b8140d4a6f4d5 Mon Sep 17 00:00:00 2001 From: CypherPoet Date: Mon, 27 Jan 2020 16:17:51 -0600 Subject: [PATCH] Setup initial app structure and data flow --- README.md | 3 +- .../MyPlayground.playground/Contents.swift | 3 + .../contents.xcplayground | 4 + .../PadFinder.xcodeproj/project.pbxproj | 531 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/swiftpm/Package.resolved | 25 + .../PadFinder/PadFinder/App/AppDelegate.swift | 37 ++ .../PadFinder/App/CurrentApplication.swift | 18 + .../PadFinder/App/SceneDelegate.swift | 67 +++ .../Base.lproj/LaunchScreen.storyboard | 25 + .../Data/Models/Pad/Pad+PadType.swift | 67 +++ .../PadFinder/Data/Models/Pad/Pad.swift | 85 +++ .../PadFinder/Data/State/AppState.swift | 36 ++ .../PadFinder/Data/State/PadsState.swift | 90 +++ .../Projects/PadFinder/PadFinder/Info.plist | 60 ++ .../Endpoint+LaunchLibraryAPI.swift | 43 ++ .../Networking/LaunchLibraryAPIService.swift | 83 +++ .../LaunchLibraryAPIServicing.swift | 16 + .../Preview Assets.xcassets/Contents.json | 6 + .../Preview Data/PreviewData+AppStore.swift | 64 +++ .../Preview Data/PreviewData.swift | 12 + .../AppIcon.appiconset/Contents.json | 98 ++++ .../Resources/Assets.xcassets/Contents.json | 6 + .../PadsListContainerView+WelcomeView.swift | 60 ++ .../Scenes/Pads/PadsListContainerView.swift | 67 +++ .../Scenes/Pads/PadsListView+ViewModel.swift | 88 +++ .../PadFinder/Scenes/Pads/PadsListView.swift | 71 +++ day-097/README.md | 28 + 29 files changed, 1707 insertions(+), 1 deletion(-) create mode 100644 day-096/Projects/MyPlayground.playground/Contents.swift create mode 100644 day-096/Projects/MyPlayground.playground/contents.xcplayground create mode 100644 day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj create mode 100644 day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 day-096/Projects/PadFinder/PadFinder/App/AppDelegate.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/App/CurrentApplication.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/App/SceneDelegate.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Base.lproj/LaunchScreen.storyboard create mode 100644 day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+PadType.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Data/State/AppState.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Data/State/PadsState.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Info.plist create mode 100644 day-096/Projects/PadFinder/PadFinder/Networking/Endpoint+LaunchLibraryAPI.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIService.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIServicing.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData+AppStore.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/Contents.json create mode 100644 day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView+WelcomeView.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView+ViewModel.swift create mode 100644 day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift create mode 100644 day-097/README.md diff --git a/README.md b/README.md index 596262d..0a56f54 100644 --- a/README.md +++ b/README.md @@ -102,12 +102,13 @@ I'm currently seeking freelance, remote opportunities as an iOS developer! If yo - **Day 93:** [_Project 18: Layout And Geometry (Part Two)_](./day-093/) - **Day 94:** [_Project 18: Layout And Geometry (Part Three)_](./day-094/) - **Day 95:** [Milestone for Projects 16-18](./day-095/) +- **Day 96:** [_Project 19: SnowSeeker (Part One)_](./day-096/) **Latest Day:** -- **Day 96:** [_Project 19: SnowSeeker (Part One)_](./day-096/) +- **Day 97:** [_Project 19: SnowSeeker (Part Two)_](./day-097/) diff --git a/day-096/Projects/MyPlayground.playground/Contents.swift b/day-096/Projects/MyPlayground.playground/Contents.swift new file mode 100644 index 0000000..49c1ff6 --- /dev/null +++ b/day-096/Projects/MyPlayground.playground/Contents.swift @@ -0,0 +1,3 @@ +import UIKit + +var str = "Hello, playground" diff --git a/day-096/Projects/MyPlayground.playground/contents.xcplayground b/day-096/Projects/MyPlayground.playground/contents.xcplayground new file mode 100644 index 0000000..9f5f2f4 --- /dev/null +++ b/day-096/Projects/MyPlayground.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a4b3dd1 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj @@ -0,0 +1,531 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */; }; + F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61923DF91C1006A5195 /* Pad+PadType.swift */; }; + F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45B23DDB0AE0061925E /* AppDelegate.swift */; }; + F331C45E23DDB0AE0061925E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45D23DDB0AE0061925E /* SceneDelegate.swift */; }; + F331C46223DDB0B00061925E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F331C46123DDB0B00061925E /* Assets.xcassets */; }; + F331C46523DDB0B00061925E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F331C46423DDB0B00061925E /* Preview Assets.xcassets */; }; + F331C47723DDB1360061925E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F331C47523DDB1360061925E /* LaunchScreen.storyboard */; }; + F331C47C23DDB1710061925E /* PadsListContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C47B23DDB1710061925E /* PadsListContainerView.swift */; }; + F331C48123DDDE080061925E /* CypherPoetSwiftUIKit in Frameworks */ = {isa = PBXBuildFile; productRef = F331C48023DDDE080061925E /* CypherPoetSwiftUIKit */; }; + F331C48323DDDE570061925E /* CurrentApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48223DDDE570061925E /* CurrentApplication.swift */; }; + F331C48523DDDE710061925E /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48423DDDE710061925E /* AppState.swift */; }; + F331C48723DDDEC30061925E /* PadsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48623DDDEC30061925E /* PadsState.swift */; }; + F331C48923DDDF0E0061925E /* Pad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48823DDDF0E0061925E /* Pad.swift */; }; + F331C48C23DDE6A90061925E /* LaunchLibraryAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48B23DDE6A90061925E /* LaunchLibraryAPIService.swift */; }; + F331C48E23DDE6C20061925E /* LaunchLibraryAPIServicing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C48D23DDE6C20061925E /* LaunchLibraryAPIServicing.swift */; }; + F331C49123DDF0A30061925E /* CypherPoetNetStack in Frameworks */ = {isa = PBXBuildFile; productRef = F331C49023DDF0A30061925E /* CypherPoetNetStack */; }; + F331C49323DDF0CF0061925E /* Endpoint+LaunchLibraryAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C49223DDF0CF0061925E /* Endpoint+LaunchLibraryAPI.swift */; }; + F331C49623DE0FED0061925E /* PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C49523DE0FED0061925E /* PreviewData.swift */; }; + F331C49823DE10010061925E /* PreviewData+AppStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C49723DE10010061925E /* PreviewData+AppStore.swift */; }; + F331C49C23DE18650061925E /* PadsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C49B23DE18650061925E /* PadsListView.swift */; }; + F331C49E23DE187A0061925E /* PadsListView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadsListContainerView+WelcomeView.swift"; sourceTree = ""; }; + F32ED61923DF91C1006A5195 /* Pad+PadType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+PadType.swift"; sourceTree = ""; }; + F331C45823DDB0AE0061925E /* PadFinder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PadFinder.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F331C45B23DDB0AE0061925E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F331C45D23DDB0AE0061925E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + F331C46123DDB0B00061925E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F331C46423DDB0B00061925E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + F331C46923DDB0B00061925E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F331C47623DDB1360061925E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = PadFinder/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; + F331C47B23DDB1710061925E /* PadsListContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadsListContainerView.swift; sourceTree = ""; }; + F331C48223DDDE570061925E /* CurrentApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentApplication.swift; sourceTree = ""; }; + F331C48423DDDE710061925E /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + F331C48623DDDEC30061925E /* PadsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadsState.swift; sourceTree = ""; }; + F331C48823DDDF0E0061925E /* Pad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pad.swift; sourceTree = ""; }; + F331C48B23DDE6A90061925E /* LaunchLibraryAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchLibraryAPIService.swift; sourceTree = ""; }; + F331C48D23DDE6C20061925E /* LaunchLibraryAPIServicing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchLibraryAPIServicing.swift; sourceTree = ""; }; + F331C49223DDF0CF0061925E /* Endpoint+LaunchLibraryAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Endpoint+LaunchLibraryAPI.swift"; sourceTree = ""; }; + F331C49523DE0FED0061925E /* PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewData.swift; sourceTree = ""; }; + F331C49723DE10010061925E /* PreviewData+AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreviewData+AppStore.swift"; sourceTree = ""; }; + F331C49B23DE18650061925E /* PadsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadsListView.swift; sourceTree = ""; }; + F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadsListView+ViewModel.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F331C45523DDB0AE0061925E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F331C49123DDF0A30061925E /* CypherPoetNetStack in Frameworks */, + F331C48123DDDE080061925E /* CypherPoetSwiftUIKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F331C44F23DDB0AE0061925E = { + isa = PBXGroup; + children = ( + F331C45A23DDB0AE0061925E /* PadFinder */, + F331C45923DDB0AE0061925E /* Products */, + ); + sourceTree = ""; + }; + F331C45923DDB0AE0061925E /* Products */ = { + isa = PBXGroup; + children = ( + F331C45823DDB0AE0061925E /* PadFinder.app */, + ); + name = Products; + sourceTree = ""; + }; + F331C45A23DDB0AE0061925E /* PadFinder */ = { + isa = PBXGroup; + children = ( + F331C47423DDB10E0061925E /* App */, + F331C47323DDB1080061925E /* Data */, + F331C47223DDB1040061925E /* Networking */, + F331C47123DDB0FF0061925E /* Reusables */, + F331C47023DDB0F80061925E /* Resources */, + F331C46F23DDB0F40061925E /* Scenes */, + F331C46923DDB0B00061925E /* Info.plist */, + F331C46323DDB0B00061925E /* Preview Content */, + ); + path = PadFinder; + sourceTree = ""; + }; + F331C46323DDB0B00061925E /* Preview Content */ = { + isa = PBXGroup; + children = ( + F331C49423DE0FE50061925E /* Preview Data */, + F331C46423DDB0B00061925E /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + F331C46F23DDB0F40061925E /* Scenes */ = { + isa = PBXGroup; + children = ( + F331C47823DDB1430061925E /* Pads */, + ); + path = Scenes; + sourceTree = ""; + }; + F331C47023DDB0F80061925E /* Resources */ = { + isa = PBXGroup; + children = ( + F331C46123DDB0B00061925E /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + F331C47123DDB0FF0061925E /* Reusables */ = { + isa = PBXGroup; + children = ( + ); + path = Reusables; + sourceTree = ""; + }; + F331C47223DDB1040061925E /* Networking */ = { + isa = PBXGroup; + children = ( + F331C48B23DDE6A90061925E /* LaunchLibraryAPIService.swift */, + F331C48D23DDE6C20061925E /* LaunchLibraryAPIServicing.swift */, + F331C49223DDF0CF0061925E /* Endpoint+LaunchLibraryAPI.swift */, + ); + path = Networking; + sourceTree = ""; + }; + F331C47323DDB1080061925E /* Data */ = { + isa = PBXGroup; + children = ( + F331C47E23DDB1D40061925E /* Models */, + F331C47D23DDB1D10061925E /* State */, + ); + path = Data; + sourceTree = ""; + }; + F331C47423DDB10E0061925E /* App */ = { + isa = PBXGroup; + children = ( + F331C45B23DDB0AE0061925E /* AppDelegate.swift */, + F331C45D23DDB0AE0061925E /* SceneDelegate.swift */, + F331C47523DDB1360061925E /* LaunchScreen.storyboard */, + F331C48223DDDE570061925E /* CurrentApplication.swift */, + ); + path = App; + sourceTree = ""; + }; + F331C47823DDB1430061925E /* Pads */ = { + isa = PBXGroup; + children = ( + F331C47B23DDB1710061925E /* PadsListContainerView.swift */, + F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */, + F331C49B23DE18650061925E /* PadsListView.swift */, + F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */, + ); + path = Pads; + sourceTree = ""; + }; + F331C47D23DDB1D10061925E /* State */ = { + isa = PBXGroup; + children = ( + F331C48423DDDE710061925E /* AppState.swift */, + F331C48623DDDEC30061925E /* PadsState.swift */, + ); + path = State; + sourceTree = ""; + }; + F331C47E23DDB1D40061925E /* Models */ = { + isa = PBXGroup; + children = ( + F331C48A23DDDF190061925E /* Pad */, + ); + path = Models; + sourceTree = ""; + }; + F331C48A23DDDF190061925E /* Pad */ = { + isa = PBXGroup; + children = ( + F331C48823DDDF0E0061925E /* Pad.swift */, + F32ED61923DF91C1006A5195 /* Pad+PadType.swift */, + ); + path = Pad; + sourceTree = ""; + }; + F331C49423DE0FE50061925E /* Preview Data */ = { + isa = PBXGroup; + children = ( + F331C49523DE0FED0061925E /* PreviewData.swift */, + F331C49723DE10010061925E /* PreviewData+AppStore.swift */, + ); + path = "Preview Data"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F331C45723DDB0AE0061925E /* PadFinder */ = { + isa = PBXNativeTarget; + buildConfigurationList = F331C46C23DDB0B00061925E /* Build configuration list for PBXNativeTarget "PadFinder" */; + buildPhases = ( + F331C45423DDB0AE0061925E /* Sources */, + F331C45523DDB0AE0061925E /* Frameworks */, + F331C45623DDB0AE0061925E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PadFinder; + packageProductDependencies = ( + F331C48023DDDE080061925E /* CypherPoetSwiftUIKit */, + F331C49023DDF0A30061925E /* CypherPoetNetStack */, + ); + productName = PadFinder; + productReference = F331C45823DDB0AE0061925E /* PadFinder.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F331C45023DDB0AE0061925E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; + ORGANIZATIONNAME = CypherPoet; + TargetAttributes = { + F331C45723DDB0AE0061925E = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = F331C45323DDB0AE0061925E /* Build configuration list for PBXProject "PadFinder" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F331C44F23DDB0AE0061925E; + packageReferences = ( + F331C47F23DDDE080061925E /* XCRemoteSwiftPackageReference "CypherPoetSwiftUIKit" */, + F331C48F23DDF0A30061925E /* XCRemoteSwiftPackageReference "CypherPoetNetStack" */, + ); + productRefGroup = F331C45923DDB0AE0061925E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F331C45723DDB0AE0061925E /* PadFinder */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F331C45623DDB0AE0061925E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F331C47723DDB1360061925E /* LaunchScreen.storyboard in Resources */, + F331C46523DDB0B00061925E /* Preview Assets.xcassets in Resources */, + F331C46223DDB0B00061925E /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F331C45423DDB0AE0061925E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F331C47C23DDB1710061925E /* PadsListContainerView.swift in Sources */, + F331C48523DDDE710061925E /* AppState.swift in Sources */, + F331C49C23DE18650061925E /* PadsListView.swift in Sources */, + F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */, + F331C48923DDDF0E0061925E /* Pad.swift in Sources */, + F331C49623DE0FED0061925E /* PreviewData.swift in Sources */, + F331C48C23DDE6A90061925E /* LaunchLibraryAPIService.swift in Sources */, + F331C49E23DE187A0061925E /* PadsListView+ViewModel.swift in Sources */, + F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */, + F331C48323DDDE570061925E /* CurrentApplication.swift in Sources */, + F331C49323DDF0CF0061925E /* Endpoint+LaunchLibraryAPI.swift in Sources */, + F331C48E23DDE6C20061925E /* LaunchLibraryAPIServicing.swift in Sources */, + F331C45E23DDB0AE0061925E /* SceneDelegate.swift in Sources */, + F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */, + F331C49823DE10010061925E /* PreviewData+AppStore.swift in Sources */, + F331C48723DDDEC30061925E /* PadsState.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + F331C47523DDB1360061925E /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F331C47623DDB1360061925E /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + F331C46A23DDB0B00061925E /* 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_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_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 = 13.2; + 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; + }; + F331C46B23DDB0B00061925E /* 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_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_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 = 13.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F331C46D23DDB0B00061925E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"PadFinder/Preview Content\""; + DEVELOPMENT_TEAM = QRXXH2RKAG; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = PadFinder/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.github.cypherpoet.PadFinder; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F331C46E23DDB0B00061925E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"PadFinder/Preview Content\""; + DEVELOPMENT_TEAM = QRXXH2RKAG; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = PadFinder/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.github.cypherpoet.PadFinder; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F331C45323DDB0AE0061925E /* Build configuration list for PBXProject "PadFinder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F331C46A23DDB0B00061925E /* Debug */, + F331C46B23DDB0B00061925E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F331C46C23DDB0B00061925E /* Build configuration list for PBXNativeTarget "PadFinder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F331C46D23DDB0B00061925E /* Debug */, + F331C46E23DDB0B00061925E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + F331C47F23DDDE080061925E /* XCRemoteSwiftPackageReference "CypherPoetSwiftUIKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.33; + }; + }; + F331C48F23DDF0A30061925E /* XCRemoteSwiftPackageReference "CypherPoetNetStack" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/CypherPoet/CypherPoetNetStack.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.27; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + F331C48023DDDE080061925E /* CypherPoetSwiftUIKit */ = { + isa = XCSwiftPackageProductDependency; + package = F331C47F23DDDE080061925E /* XCRemoteSwiftPackageReference "CypherPoetSwiftUIKit" */; + productName = CypherPoetSwiftUIKit; + }; + F331C49023DDF0A30061925E /* CypherPoetNetStack */ = { + isa = XCSwiftPackageProductDependency; + package = F331C48F23DDF0A30061925E /* XCRemoteSwiftPackageReference "CypherPoetNetStack" */; + productName = CypherPoetNetStack; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = F331C45023DDB0AE0061925E /* Project object */; +} diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..b43736d --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "CypherPoetNetStack", + "repositoryURL": "https://github.com/CypherPoet/CypherPoetNetStack.git", + "state": { + "branch": null, + "revision": "9852c07ae3c6e4294e1a2d277b6c83cb3515eb58", + "version": "0.0.27" + } + }, + { + "package": "CypherPoetSwiftUIKit", + "repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git", + "state": { + "branch": null, + "revision": "65d4268cddbedfeabe44cd625b35bd1812a37587", + "version": "0.0.33" + } + } + ] + }, + "version": 1 +} diff --git a/day-096/Projects/PadFinder/PadFinder/App/AppDelegate.swift b/day-096/Projects/PadFinder/PadFinder/App/AppDelegate.swift new file mode 100644 index 0000000..c75ae26 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/App/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// PadFinder +// +// Created by Brian Sipple on 1/26/20. +// Copyright © 2020 CypherPoet. All rights reserved. +// + +import UIKit + +@UIApplicationMain +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. + } + + +} + diff --git a/day-096/Projects/PadFinder/PadFinder/App/CurrentApplication.swift b/day-096/Projects/PadFinder/PadFinder/App/CurrentApplication.swift new file mode 100644 index 0000000..308789d --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/App/CurrentApplication.swift @@ -0,0 +1,18 @@ +// +// CurrentApplication.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation + +struct CurrentApplication { + var launchLibraryAPIService: LaunchLibraryAPIService +} + + +var CurrentApp = CurrentApplication( + launchLibraryAPIService: LaunchLibraryAPIService() +) diff --git a/day-096/Projects/PadFinder/PadFinder/App/SceneDelegate.swift b/day-096/Projects/PadFinder/PadFinder/App/SceneDelegate.swift new file mode 100644 index 0000000..079d673 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/App/SceneDelegate.swift @@ -0,0 +1,67 @@ +// +// SceneDelegate.swift +// PadFinder +// +// Created by Brian Sipple on 1/26/20. +// Copyright © 2020 CypherPoet. All rights reserved. +// + +import UIKit +import SwiftUI + +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). + + // Use a UIHostingController as window root view controller. + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + let store = AppStore(initialState: .init(), appReducer: appReducer) + + // Create the SwiftUI view that provides the window contents. + let entryView = PadsListContainerView() + .environmentObject(store) + + window.rootViewController = UIHostingController(rootView: entryView) + + self.window = window + 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 neccessarily 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. + } +} + diff --git a/day-096/Projects/PadFinder/PadFinder/Base.lproj/LaunchScreen.storyboard b/day-096/Projects/PadFinder/PadFinder/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+PadType.swift b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+PadType.swift new file mode 100644 index 0000000..7a496ed --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+PadType.swift @@ -0,0 +1,67 @@ +// +// Pad+PadType.swift +// PadFinder +// +// Created by CypherPoet on 1/27/20. +// ✌️ +// + +import Foundation +import SwiftUI + + +extension Pad { + enum PadType: Int, Decodable { + case launch = 0 + case landing = 1 + } +} + +extension Pad.PadType: CaseIterable {} + +extension Pad.PadType: Identifiable { + var id: Int { self.rawValue } +} + + +extension Pad.PadType { + + var displayName: String { + switch self { + case .launch: + return "Launch Pad" + case .landing: + return "Landing Pad" + } + } + + + var sfSymbolName: String { + switch self { + case .launch: + return "chevron.up" + case .landing: + return "chevron.down" + } + } + + + var listItemBackgroundColor: Color { + switch self { + case .launch: + return .purple + case .landing: + return .orange + } + } + + + var listItemImage: some View { + Image(systemName: sfSymbolName) + .font(.title) + .foregroundColor(.white) + .padding(11) + .background(listItemBackgroundColor) + .clipShape(Circle()) + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift new file mode 100644 index 0000000..1028b6f --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift @@ -0,0 +1,85 @@ +// +// Pad.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation +import CoreLocation + + +struct Pad { + let id: Int + var name: String + var padType: PadType + var mapURL: URL + var latitude: CLLocationDegrees + var longitude: CLLocationDegrees + var isRetired: Bool + var infoURLs: [URL] +} + + +extension Pad: Identifiable {} + + +extension Pad { + static func isRetired(int: Int) -> Bool { + int == 1 + } +} + +// MARK: - Decodable +extension Pad: Decodable { + struct ResultsContainer: Decodable { + var pads: [Pad] + } + + enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case padType = "padType" + case mapURL = "mapURL" + case latitude = "latitude" + case longitude = "longitude" + case isRetired = "retired" + case infoURLs = "infoURLs" + } + + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + id = try container.decode(Int.self, forKey: .id) + name = try container.decode(String.self, forKey: .name) + padType = try container.decode(PadType.self, forKey: .padType) + + let mapURLString = try container.decode(String.self, forKey: .mapURL) + .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + + mapURL = URL(string: mapURLString)! +// if mapURL == nil { +// print("failed to make URL from string \(mapURLString)") +// } + + let latitudeString = try container.decode(String.self, forKey: .latitude) + latitude = CLLocationDegrees(latitudeString) ?? 0.0 + + let longitudeString = try container.decode(String.self, forKey: .longitude) + longitude = CLLocationDegrees(longitudeString) ?? 0.0 + + let isRetiredInt = try container.decode(Int.self, forKey: .isRetired) + + isRetired = Pad.isRetired(int: isRetiredInt) + infoURLs = try container.decodeIfPresent([URL].self, forKey: .infoURLs) ?? [] + } +} + + +extension Pad { + static var decoder: JSONDecoder { + .init() + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Data/State/AppState.swift b/day-096/Projects/PadFinder/PadFinder/Data/State/AppState.swift new file mode 100644 index 0000000..e03b4de --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/State/AppState.swift @@ -0,0 +1,36 @@ +// +// AppState.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation +import CypherPoetSwiftUIKit_DataFlowUtils + + + +struct AppState { + var padsState = PadsState() +} + + + +//enum AppSideEffect: SideEffect {} + +enum AppAction { + case pads(_ action: PadsAction) +} + + +// MARK: - Reducer +let appReducer = Reducer { appState, action in + switch action { + case .pads(let action): + padsReducer.reduce(&appState.padsState, action) + } +} + + +typealias AppStore = Store diff --git a/day-096/Projects/PadFinder/PadFinder/Data/State/PadsState.swift b/day-096/Projects/PadFinder/PadFinder/Data/State/PadsState.swift new file mode 100644 index 0000000..4c04d3e --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/State/PadsState.swift @@ -0,0 +1,90 @@ +// +// PadsState.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + + +import Foundation +import Combine +import CypherPoetSwiftUIKit_DataFlowUtils + + +struct PadsState { + var dataFetchingState: DataFetchingState = .inactive +} + + + +extension PadsState { + enum DataFetchingState { + case inactive + case fetching + case fetched([Pad]) + case errored(Error) + } +} + +extension PadsState.DataFetchingState: Equatable { + + static func == ( + lhs: PadsState.DataFetchingState, + rhs: PadsState.DataFetchingState + ) -> Bool { + switch (lhs, rhs) { + case (.inactive, .inactive), + (.fetching, .fetching), + (.fetched, .fetched), + (.errored, .errored): + return true + default: + return false + } + } + + +} + + +enum PadsSideEffect: SideEffect { + case fetchPads + + func mapToAction() -> AnyPublisher { + switch self { + case .fetchPads: + return CurrentApp.launchLibraryAPIService + .pads() + .map { AppAction.pads(.fetchedPadsSet($0)) } + .catch { error in + Just(AppAction.pads(.fetchErrorSet(error))) + } + .eraseToAnyPublisher() + } + } +} + + + +enum PadsAction { + case padsFetchStart + case fetchedPadsSet([Pad]) + case fetchErrorSet(Error) +} + + +// MARK: - Reducer +let padsReducer: Reducer = Reducer( + reduce: { state, action in + switch action { + case .fetchedPadsSet(let pads): + state.dataFetchingState = .fetched(pads) + case .padsFetchStart: + state.dataFetchingState = .fetching + case .fetchErrorSet(let error): + state.dataFetchingState = .errored(error) + } + } +) + diff --git a/day-096/Projects/PadFinder/PadFinder/Info.plist b/day-096/Projects/PadFinder/PadFinder/Info.plist new file mode 100644 index 0000000..9742bf0 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/day-096/Projects/PadFinder/PadFinder/Networking/Endpoint+LaunchLibraryAPI.swift b/day-096/Projects/PadFinder/PadFinder/Networking/Endpoint+LaunchLibraryAPI.swift new file mode 100644 index 0000000..9c1503b --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Networking/Endpoint+LaunchLibraryAPI.swift @@ -0,0 +1,43 @@ +// +// Endpoint+LaunchLibraryAPI.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation +import CypherPoetNetStack + + +extension Endpoint { + + enum LaunchLibraryAPI { + private static let host = "launchlibrary.net" + private static let basePath = "/1.4" + + + public static func pads( + sizeMode: VerbosityMode = .summary + ) -> Endpoint { + .init( + host: self.host, + path: "\(self.basePath)/pad", + queryItems: [ + URLQueryItem(name: "mode", value: sizeMode.rawValue) + ] + ) + } + } +} + + +extension Endpoint.LaunchLibraryAPI { + + /// Specifies how much data to return in each result item + enum VerbosityMode: String { + case list + case summary + case verbose + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIService.swift b/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIService.swift new file mode 100644 index 0000000..0660d7b --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIService.swift @@ -0,0 +1,83 @@ +// +// LaunchLibraryAPIService.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation +import Combine +import CypherPoetNetStack + + +final class LaunchLibraryAPIService: LaunchLibraryAPIServicing { + var session: URLSession + var apiQueue: DispatchQueue + + + init( + session: URLSession = .shared, + queue: DispatchQueue = DispatchQueue(label: "LaunchLibraryAPIService", qos: .userInitiated) + ) { + self.session = session + self.apiQueue = queue + } +} + + + +extension LaunchLibraryAPIService { + + private func padsContainer( + using decoder: JSONDecoder = Pad.decoder + ) -> AnyPublisher { + let endpoint = Endpoint.LaunchLibraryAPI.pads() + + guard let url = endpoint.url else { + fatalError("Failed to make URL for pads.") + } + + return perform( + URLRequest(url: url), + parsingResponseOn: apiQueue, + with: decoder + ) + .mapError { Error.network(error: $0) } + .eraseToAnyPublisher() + } + + + func pads( + using decoder: JSONDecoder = Pad.decoder + ) -> AnyPublisher<[Pad], Swift.Error> { + padsContainer(using: decoder) + .map(\.pads) + .eraseToAnyPublisher() + } +} + + +// MARK: - Error +extension LaunchLibraryAPIService { + + enum Error: LocalizedError { + case network(error: NetStackError) + } +} + + +extension LaunchLibraryAPIService.Error { + public var errorDescription: String? { + switch self { + case .network(let error): + return error.errorDescription + } + } +} + + +// MARK: - Error: Identifiable +extension LaunchLibraryAPIService.Error: Identifiable { + public var id: String? { errorDescription } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIServicing.swift b/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIServicing.swift new file mode 100644 index 0000000..7f25675 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Networking/LaunchLibraryAPIServicing.swift @@ -0,0 +1,16 @@ +// +// LaunchLibraryAPIServicing.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation +import Combine +import CypherPoetNetStack + + +protocol LaunchLibraryAPIServicing: ModelTransportRequestPublishing { + func pads(using decoder: JSONDecoder) -> AnyPublisher<[Pad], Error> +} diff --git a/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Assets.xcassets/Contents.json b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData+AppStore.swift b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData+AppStore.swift new file mode 100644 index 0000000..16cdc11 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData+AppStore.swift @@ -0,0 +1,64 @@ +// +// PreviewData+AppStore.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation + + +extension PreviewData { + + enum Pads { + static let pad1 = Pad( + id: 166, + name: "Rocket Lab Launch Complex 1", + padType: .landing, +// mapURL: URL(string: #"https:\/\/twitter.com\/rocketlab"#.replacingOccurrences(of: #"\"#, with: ""))!, + mapURL: URL( + string: #"https:\/\/www.google.ee\/maps\/place\/39°15'46.2\"S+177°51'52.1\"E\/"# + .replacingOccurrences(of: #"\"#, with: "") + .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + )!, + latitude: -39.262833000000000, + longitude: 177.864469000000000, + isRetired: false, + infoURLs: [ + URL(string: #"http:\/\/www.rocketlabusa.com\/"#.replacingOccurrences(of: #"\"#, with: ""))!, + URL(string: #"https:\/\/twitter.com\/rocketlab"#.replacingOccurrences(of: #"\"#, with: ""))!, + URL(string: #"https:\/\/www.youtube.com\/user\/RocketLabNZ"#.replacingOccurrences(of: #"\"#, with: ""))!, + URL(string: #"https:\/\/www.facebook.com\/RocketLabUSA"#.replacingOccurrences(of: #"\"#, with: ""))!, + URL(string: #"https:\/\/www.linkedin.com\/company\/rocket-lab-limited"#.replacingOccurrences(of: #"\"#, with: ""))!, + ] + ) + } + + enum PadsStates { + static let `default`: PadsState = { + .init( + dataFetchingState: .fetched([ + PreviewData.Pads.pad1, + ]) + ) + }() + } + + + enum AppStores { + static let empty: AppStore = { + AppStore(initialState: .init(), appReducer: appReducer) + }() + + + static let withPads: AppStore = { + AppStore( + initialState: .init( + padsState: PreviewData.PadsStates.default + ), + appReducer: appReducer + ) + }() + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData.swift b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData.swift new file mode 100644 index 0000000..79fd16f --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Preview Content/Preview Data/PreviewData.swift @@ -0,0 +1,12 @@ +// +// PreviewData.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import Foundation + + +enum PreviewData {} diff --git a/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "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" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/Contents.json b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView+WelcomeView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView+WelcomeView.swift new file mode 100644 index 0000000..deb7917 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView+WelcomeView.swift @@ -0,0 +1,60 @@ +// +// WelcomeView.swift +// PadFinder +// +// Created by CypherPoet on 1/27/20. +// ✌️ +// + +import SwiftUI + + +extension PadsListContainerView { + struct WelcomeView { + + } +} + + +// MARK: - View +extension PadsListContainerView.WelcomeView: View { + + var body: some View { + + VStack(spacing: 20.0) { + Text("Welcome to PadFinder! 🚀") + .font(.largeTitle) + + Text("Select a launch pad from the side menu to get started. (Try swiping from the left edge if you don't see it!)") + .font(.headline) + .foregroundColor(.secondary) + + } + .padding() + } +} + + +// MARK: - Computeds +extension PadsListContainerView.WelcomeView { +} + + +// MARK: - View Variables +extension PadsListContainerView.WelcomeView { +} + + +// MARK: - Private Helpers +private extension PadsListContainerView.WelcomeView { +} + + +// MARK: - Preview +struct PadsListContainerView_WelcomeView_Previews: PreviewProvider { + + static var previews: some View { + PadsListContainerView.WelcomeView() + } +} + diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift new file mode 100644 index 0000000..1511074 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift @@ -0,0 +1,67 @@ +// +// PadsListContainerView.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import SwiftUI + + +struct PadsListContainerView { + @EnvironmentObject private var store: AppStore +} + + +// MARK: - View +extension PadsListContainerView: View { + + var body: some View { + NavigationView { + PadsListView(viewModel: .init(padsState: padsState)) + .navigationBarTitle("Launch Pads") + + WelcomeView() + } + .onAppear(perform: fetchPads) + } +} + + +// MARK: - Computeds +extension PadsListContainerView { + + var padsState: PadsState { store.state.padsState } +} + + +// MARK: - View Variables +extension PadsListContainerView { +} + + +// MARK: - Private Helpers +private extension PadsListContainerView { + + func fetchPads() { + store.send(PadsSideEffect.fetchPads) + } +} + + + +// MARK: - Preview +struct PadsListContainerView_Previews: PreviewProvider { + + static var previews: some View { + let store = PreviewData.AppStores.withPads + + return PadsListContainerView( +// viewModel: .init( +// padsState: store.state.padsState +// ) + ) + .environmentObject(store) + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView+ViewModel.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView+ViewModel.swift new file mode 100644 index 0000000..4cf9662 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView+ViewModel.swift @@ -0,0 +1,88 @@ +// +// PadsListView+ViewModel.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + + +import SwiftUI +import Combine + + +extension PadsListView { + final class ViewModel: ObservableObject { + private var subscriptions = Set() + + private let padsState: PadsState + + + // MARK: - Published Outputs + @Published var pads: [Pad] = [] + + + // MARK: - Init + init( + padsState: PadsState = .init() + ) { + self.padsState = padsState + + setupSubscribers() + } + } +} + + +// MARK: - Publishers +extension PadsListView.ViewModel { + + private var padsStatePublisher: Publishers.Share> { + CurrentValueSubject(padsState) +// .print("padsStatePublisher") + .eraseToAnyPublisher() + .share() + } + + + private var padsFetchingStatePublisher: Publishers.Share> { + padsStatePublisher + .map(\.dataFetchingState) + .eraseToAnyPublisher() + .share() + } +} + + +// MARK: - Computeds +extension PadsListView.ViewModel { +} + + +// MARK: - Public Methods +extension PadsListView.ViewModel { +} + + + +// MARK: - Private Helpers +private extension PadsListView.ViewModel { + + func setupSubscribers() { + padsFetchingStatePublisher + .receive(on: DispatchQueue.main) + .sink(receiveValue: { (fetchingState: PadsState.DataFetchingState) in + switch fetchingState { + case .inactive: + self.pads = [] + case .fetching: + self.pads = [] + case .fetched(let pads): + self.pads = pads + case .errored(_): + fatalError() + } + }) + .store(in: &subscriptions) + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift new file mode 100644 index 0000000..0744f98 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift @@ -0,0 +1,71 @@ +// +// PadsListView.swift +// PadFinder +// +// Created by CypherPoet on 1/26/20. +// ✌️ +// + +import SwiftUI + + +struct PadsListView { + @EnvironmentObject private var store: AppStore + + @ObservedObject var viewModel: ViewModel +} + + +// MARK: - View +extension PadsListView: View { + + var body: some View { + List(viewModel.pads) { pad in + NavigationLink(destination: Text(pad.name)) { + HStack { + pad.padType.listItemImage + + VStack(alignment: .leading) { + Text(pad.name) + .foregroundColor(.primary) + .font(.headline) + + Text(pad.padType.displayName) + .font(.subheadline) + .foregroundColor(.secondary) + } + } + } + } + } +} + + +// MARK: - Computeds +extension PadsListView { +} + + +// MARK: - View Variables +extension PadsListView { +} + + +// MARK: - Private Helpers +private extension PadsListView { +} + + + +// MARK: - Preview +struct PadsListView_Previews: PreviewProvider { + + static var previews: some View { + let store = PreviewData.AppStores.withPads + + return PadsListView( + viewModel: .init(padsState: store.state.padsState) + ) + .environmentObject(store) + } +} diff --git a/day-097/README.md b/day-097/README.md new file mode 100644 index 0000000..a4345b7 --- /dev/null +++ b/day-097/README.md @@ -0,0 +1,28 @@ +# Day 97: _Project 19: SnowSeeker_ (Part Two) + +_Follow along at https://www.hackingwithswift.com/100/swiftui/97_. + +
+ + +# 📒 Field Notes + +This day covers Part Two of _`Project 18`_ in the [100 Days of SwiftUI Challenge](https://www.hackingwithswift.com/100/swiftui/97). (Project 18 files can be found in the [directory for Part One](../day-092/).) + +It focuses on several specific topics: + +- Absolute positioning for SwiftUI views +- Understanding frames and coordinates inside GeometryReader +- ScrollView effects using GeometryReader + + + +The commits for most of the changes related to this day can be found [here](https://github.com/CypherPoet/100-days-of-swiftui-and-combine/commit/013ce270c007a6b432a739a55771acf24daab0d1). + + + +# 📸 Screenshots + +
+ +