Begin implementing Pad details view.
This commit is contained in:
parent
8faf051801
commit
922337f7e6
|
@ -9,6 +9,13 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */; };
|
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 */; };
|
F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61923DF91C1006A5195 /* Pad+PadType.swift */; };
|
||||||
|
F32ED61D23DF9B46006A5195 /* PadDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61C23DF9B46006A5195 /* PadDetailsView.swift */; };
|
||||||
|
F32ED61F23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED61E23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift */; };
|
||||||
|
F32ED62223DFB836006A5195 /* NumberFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED62123DFB836006A5195 /* NumberFormatters.swift */; };
|
||||||
|
F32ED62423DFC7D5006A5195 /* Pad+SnapshotUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED62323DFC7D5006A5195 /* Pad+SnapshotUtils.swift */; };
|
||||||
|
F32ED62623E0D525006A5195 /* Pad+Computeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED62523E0D525006A5195 /* Pad+Computeds.swift */; };
|
||||||
|
F32ED62923E0F0C6006A5195 /* MapSnapshottingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED62823E0F0C6006A5195 /* MapSnapshottingService.swift */; };
|
||||||
|
F32ED62B23E0F0F1006A5195 /* MapSnapshotServicing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32ED62A23E0F0F1006A5195 /* MapSnapshotServicing.swift */; };
|
||||||
F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45B23DDB0AE0061925E /* AppDelegate.swift */; };
|
F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45B23DDB0AE0061925E /* AppDelegate.swift */; };
|
||||||
F331C45E23DDB0AE0061925E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45D23DDB0AE0061925E /* SceneDelegate.swift */; };
|
F331C45E23DDB0AE0061925E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45D23DDB0AE0061925E /* SceneDelegate.swift */; };
|
||||||
F331C46223DDB0B00061925E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F331C46123DDB0B00061925E /* Assets.xcassets */; };
|
F331C46223DDB0B00061925E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F331C46123DDB0B00061925E /* Assets.xcassets */; };
|
||||||
|
@ -33,6 +40,13 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadsListContainerView+WelcomeView.swift"; sourceTree = "<group>"; };
|
F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadsListContainerView+WelcomeView.swift"; sourceTree = "<group>"; };
|
||||||
F32ED61923DF91C1006A5195 /* Pad+PadType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+PadType.swift"; sourceTree = "<group>"; };
|
F32ED61923DF91C1006A5195 /* Pad+PadType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+PadType.swift"; sourceTree = "<group>"; };
|
||||||
|
F32ED61C23DF9B46006A5195 /* PadDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadDetailsView.swift; sourceTree = "<group>"; };
|
||||||
|
F32ED61E23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadDetailsView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||||
|
F32ED62123DFB836006A5195 /* NumberFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatters.swift; sourceTree = "<group>"; };
|
||||||
|
F32ED62323DFC7D5006A5195 /* Pad+SnapshotUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+SnapshotUtils.swift"; sourceTree = "<group>"; };
|
||||||
|
F32ED62523E0D525006A5195 /* Pad+Computeds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+Computeds.swift"; sourceTree = "<group>"; };
|
||||||
|
F32ED62823E0F0C6006A5195 /* MapSnapshottingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshottingService.swift; sourceTree = "<group>"; };
|
||||||
|
F32ED62A23E0F0F1006A5195 /* MapSnapshotServicing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshotServicing.swift; sourceTree = "<group>"; };
|
||||||
F331C45823DDB0AE0061925E /* PadFinder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PadFinder.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
F331C45B23DDB0AE0061925E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
F331C45D23DDB0AE0061925E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
F331C45D23DDB0AE0061925E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -67,6 +81,32 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
F32ED61B23DF9B36006A5195 /* Pad Details */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F32ED61C23DF9B46006A5195 /* PadDetailsView.swift */,
|
||||||
|
F32ED61E23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift */,
|
||||||
|
);
|
||||||
|
path = "Pad Details";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F32ED62023DFB7E9006A5195 /* Formatters */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F32ED62123DFB836006A5195 /* NumberFormatters.swift */,
|
||||||
|
);
|
||||||
|
path = Formatters;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F32ED62723E0F0B8006A5195 /* Services */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F32ED62823E0F0C6006A5195 /* MapSnapshottingService.swift */,
|
||||||
|
F32ED62A23E0F0F1006A5195 /* MapSnapshotServicing.swift */,
|
||||||
|
);
|
||||||
|
path = Services;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F331C44F23DDB0AE0061925E = {
|
F331C44F23DDB0AE0061925E = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -126,6 +166,8 @@
|
||||||
F331C47123DDB0FF0061925E /* Reusables */ = {
|
F331C47123DDB0FF0061925E /* Reusables */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F32ED62723E0F0B8006A5195 /* Services */,
|
||||||
|
F32ED62023DFB7E9006A5195 /* Formatters */,
|
||||||
);
|
);
|
||||||
path = Reusables;
|
path = Reusables;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -164,6 +206,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F331C47B23DDB1710061925E /* PadsListContainerView.swift */,
|
F331C47B23DDB1710061925E /* PadsListContainerView.swift */,
|
||||||
|
F32ED61B23DF9B36006A5195 /* Pad Details */,
|
||||||
F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */,
|
F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */,
|
||||||
F331C49B23DE18650061925E /* PadsListView.swift */,
|
F331C49B23DE18650061925E /* PadsListView.swift */,
|
||||||
F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */,
|
F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */,
|
||||||
|
@ -192,6 +235,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F331C48823DDDF0E0061925E /* Pad.swift */,
|
F331C48823DDDF0E0061925E /* Pad.swift */,
|
||||||
|
F32ED62323DFC7D5006A5195 /* Pad+SnapshotUtils.swift */,
|
||||||
|
F32ED62523E0D525006A5195 /* Pad+Computeds.swift */,
|
||||||
F32ED61923DF91C1006A5195 /* Pad+PadType.swift */,
|
F32ED61923DF91C1006A5195 /* Pad+PadType.swift */,
|
||||||
);
|
);
|
||||||
path = Pad;
|
path = Pad;
|
||||||
|
@ -287,11 +332,17 @@
|
||||||
files = (
|
files = (
|
||||||
F331C47C23DDB1710061925E /* PadsListContainerView.swift in Sources */,
|
F331C47C23DDB1710061925E /* PadsListContainerView.swift in Sources */,
|
||||||
F331C48523DDDE710061925E /* AppState.swift in Sources */,
|
F331C48523DDDE710061925E /* AppState.swift in Sources */,
|
||||||
|
F32ED62923E0F0C6006A5195 /* MapSnapshottingService.swift in Sources */,
|
||||||
|
F32ED61F23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift in Sources */,
|
||||||
F331C49C23DE18650061925E /* PadsListView.swift in Sources */,
|
F331C49C23DE18650061925E /* PadsListView.swift in Sources */,
|
||||||
F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */,
|
F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */,
|
||||||
F331C48923DDDF0E0061925E /* Pad.swift in Sources */,
|
F331C48923DDDF0E0061925E /* Pad.swift in Sources */,
|
||||||
F331C49623DE0FED0061925E /* PreviewData.swift in Sources */,
|
F331C49623DE0FED0061925E /* PreviewData.swift in Sources */,
|
||||||
|
F32ED62423DFC7D5006A5195 /* Pad+SnapshotUtils.swift in Sources */,
|
||||||
F331C48C23DDE6A90061925E /* LaunchLibraryAPIService.swift in Sources */,
|
F331C48C23DDE6A90061925E /* LaunchLibraryAPIService.swift in Sources */,
|
||||||
|
F32ED62B23E0F0F1006A5195 /* MapSnapshotServicing.swift in Sources */,
|
||||||
|
F32ED62623E0D525006A5195 /* Pad+Computeds.swift in Sources */,
|
||||||
|
F32ED62223DFB836006A5195 /* NumberFormatters.swift in Sources */,
|
||||||
F331C49E23DE187A0061925E /* PadsListView+ViewModel.swift in Sources */,
|
F331C49E23DE187A0061925E /* PadsListView+ViewModel.swift in Sources */,
|
||||||
F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */,
|
F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */,
|
||||||
F331C48323DDDE570061925E /* CurrentApplication.swift in Sources */,
|
F331C48323DDDE570061925E /* CurrentApplication.swift in Sources */,
|
||||||
|
@ -301,6 +352,7 @@
|
||||||
F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */,
|
F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */,
|
||||||
F331C49823DE10010061925E /* PreviewData+AppStore.swift in Sources */,
|
F331C49823DE10010061925E /* PreviewData+AppStore.swift in Sources */,
|
||||||
F331C48723DDDEC30061925E /* PadsState.swift in Sources */,
|
F331C48723DDDEC30061925E /* PadsState.swift in Sources */,
|
||||||
|
F32ED61D23DF9B46006A5195 /* PadDetailsView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git",
|
"repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "65d4268cddbedfeabe44cd625b35bd1812a37587",
|
"revision": "dca4353f34bdf5a622fd5fc7c31dc72ad377194a",
|
||||||
"version": "0.0.33"
|
"version": "0.0.35"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1130"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F331C45723DDB0AE0061925E"
|
||||||
|
BuildableName = "PadFinder.app"
|
||||||
|
BlueprintName = "PadFinder"
|
||||||
|
ReferencedContainer = "container:PadFinder.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F331C45723DDB0AE0061925E"
|
||||||
|
BuildableName = "PadFinder.app"
|
||||||
|
BlueprintName = "PadFinder"
|
||||||
|
ReferencedContainer = "container:PadFinder.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F331C45723DDB0AE0061925E"
|
||||||
|
BuildableName = "PadFinder.app"
|
||||||
|
BlueprintName = "PadFinder"
|
||||||
|
ReferencedContainer = "container:PadFinder.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Pad+Computeds.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/28/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
|
|
||||||
|
extension Pad {
|
||||||
|
|
||||||
|
var coordinate: CLLocationCoordinate2D {
|
||||||
|
.init(latitude: latitude, longitude: longitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Pad+SnapshotUtils.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/27/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MapKit
|
||||||
|
|
||||||
|
|
||||||
|
extension Pad {
|
||||||
|
|
||||||
|
var baseSnapshotOptions: MKMapSnapshotter.Options {
|
||||||
|
let options = MKMapSnapshotter.Options()
|
||||||
|
|
||||||
|
options.mapType = .standard
|
||||||
|
options.scale = UIScreen.main.scale
|
||||||
|
|
||||||
|
options.showsBuildings = true
|
||||||
|
// options.pointOfInterestFilter = .init(including: [.airport, .cafe])
|
||||||
|
options.pointOfInterestFilter = .includingAll
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,10 +15,11 @@ struct Pad {
|
||||||
var name: String
|
var name: String
|
||||||
var padType: PadType
|
var padType: PadType
|
||||||
var mapURL: URL
|
var mapURL: URL
|
||||||
|
var wikiURL: URL?
|
||||||
var latitude: CLLocationDegrees
|
var latitude: CLLocationDegrees
|
||||||
var longitude: CLLocationDegrees
|
var longitude: CLLocationDegrees
|
||||||
var isRetired: Bool
|
var isRetired: Bool
|
||||||
var infoURLs: [URL]
|
var infoURLs: [URL]?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ extension Pad: Decodable {
|
||||||
case name = "name"
|
case name = "name"
|
||||||
case padType = "padType"
|
case padType = "padType"
|
||||||
case mapURL = "mapURL"
|
case mapURL = "mapURL"
|
||||||
|
case wikiURL = "wikiURL"
|
||||||
case latitude = "latitude"
|
case latitude = "latitude"
|
||||||
case longitude = "longitude"
|
case longitude = "longitude"
|
||||||
case isRetired = "retired"
|
case isRetired = "retired"
|
||||||
|
@ -64,6 +66,14 @@ extension Pad: Decodable {
|
||||||
// print("failed to make URL from string \(mapURLString)")
|
// print("failed to make URL from string \(mapURLString)")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if let wikiURLString = try? container
|
||||||
|
.decode(String.self, forKey: .wikiURL)
|
||||||
|
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
||||||
|
{
|
||||||
|
wikiURL = URL(string: wikiURLString)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let latitudeString = try container.decode(String.self, forKey: .latitude)
|
let latitudeString = try container.decode(String.self, forKey: .latitude)
|
||||||
latitude = CLLocationDegrees(latitudeString) ?? 0.0
|
latitude = CLLocationDegrees(latitudeString) ?? 0.0
|
||||||
|
|
||||||
|
@ -73,7 +83,7 @@ extension Pad: Decodable {
|
||||||
let isRetiredInt = try container.decode(Int.self, forKey: .isRetired)
|
let isRetiredInt = try container.decode(Int.self, forKey: .isRetired)
|
||||||
|
|
||||||
isRetired = Pad.isRetired(int: isRetiredInt)
|
isRetired = Pad.isRetired(int: isRetiredInt)
|
||||||
infoURLs = try container.decodeIfPresent([URL].self, forKey: .infoURLs) ?? []
|
infoURLs = try container.decodeIfPresent([URL].self, forKey: .infoURLs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ extension PreviewData {
|
||||||
id: 166,
|
id: 166,
|
||||||
name: "Rocket Lab Launch Complex 1",
|
name: "Rocket Lab Launch Complex 1",
|
||||||
padType: .landing,
|
padType: .landing,
|
||||||
// mapURL: URL(string: #"https:\/\/twitter.com\/rocketlab"#.replacingOccurrences(of: #"\"#, with: ""))!,
|
|
||||||
mapURL: URL(
|
mapURL: URL(
|
||||||
string: #"https:\/\/www.google.ee\/maps\/place\/39°15'46.2\"S+177°51'52.1\"E\/"#
|
string: #"https:\/\/www.google.ee\/maps\/place\/39°15'46.2\"S+177°51'52.1\"E\/"#
|
||||||
.replacingOccurrences(of: #"\"#, with: "")
|
.replacingOccurrences(of: #"\"#, with: "")
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "CanaveralMapSample.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// Double+CoordinateFormat.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/27/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
enum NumberFormatters {
|
||||||
|
|
||||||
|
static var padCoordinateDisplay: NumberFormatter {
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
|
||||||
|
formatter.numberStyle = .decimal
|
||||||
|
formatter.maximumFractionDigits = 4
|
||||||
|
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
//
|
||||||
|
// MapSnapshotServicing.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/28/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MapKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
protocol MapSnapshotServicing: class {
|
||||||
|
var snapshotOptions: MKMapSnapshotter.Options { get }
|
||||||
|
var queue: DispatchQueue { get }
|
||||||
|
|
||||||
|
func takeSnapshot(
|
||||||
|
with size: CGSize,
|
||||||
|
at coordinate: CLLocationCoordinate2D,
|
||||||
|
latitudeSpan: CLLocationDegrees,
|
||||||
|
longitudeSpan: CLLocationDegrees
|
||||||
|
) -> Future<MKMapSnapshotter.Snapshot, Error>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension MapSnapshotServicing {
|
||||||
|
|
||||||
|
func takeSnapshot(
|
||||||
|
with size: CGSize,
|
||||||
|
at coordinate: CLLocationCoordinate2D,
|
||||||
|
latitudeSpan: CLLocationDegrees = 0.15,
|
||||||
|
longitudeSpan: CLLocationDegrees = 0.15
|
||||||
|
) -> Future<MKMapSnapshotter.Snapshot, Error> {
|
||||||
|
let span = MKCoordinateSpan(latitudeDelta: latitudeSpan, longitudeDelta: longitudeSpan)
|
||||||
|
|
||||||
|
snapshotOptions.region = MKCoordinateRegion(
|
||||||
|
center: coordinate,
|
||||||
|
span: span
|
||||||
|
)
|
||||||
|
|
||||||
|
snapshotOptions.size = size
|
||||||
|
|
||||||
|
let snapshotter = MKMapSnapshotter(options: snapshotOptions)
|
||||||
|
|
||||||
|
return Future { promise in
|
||||||
|
snapshotter.start(with: self.queue) { (snapshot, error) in
|
||||||
|
guard error == nil else {
|
||||||
|
return promise(.failure(error!))
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let snapshot = snapshot else {
|
||||||
|
preconditionFailure("No snapshot returned despite snapshotter completing without error.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise(.success(snapshot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// MapSnapshottingService.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/28/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MapKit
|
||||||
|
|
||||||
|
|
||||||
|
final class MapSnapshottingService: MapSnapshotServicing {
|
||||||
|
var snapshotOptions: MKMapSnapshotter.Options
|
||||||
|
var queue: DispatchQueue
|
||||||
|
|
||||||
|
|
||||||
|
init(
|
||||||
|
snapshotOptions: MKMapSnapshotter.Options = .init(),
|
||||||
|
queue: DispatchQueue = DispatchQueue(label: "Map Snapshotting Service", qos: .default)
|
||||||
|
) {
|
||||||
|
self.snapshotOptions = snapshotOptions
|
||||||
|
self.queue = queue
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
//
|
||||||
|
// PadDetailsView+ViewModel.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/27/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MapKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
extension PadDetailsView {
|
||||||
|
final class ViewModel: ObservableObject {
|
||||||
|
private var subscriptions = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private let pad: Pad
|
||||||
|
private let snapshotService: MapSnapshotServicing
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Published Outputs
|
||||||
|
@Published var mapSnapshotImage: UIImage?
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
init(
|
||||||
|
pad: Pad,
|
||||||
|
snapshotService: MapSnapshotServicing
|
||||||
|
) {
|
||||||
|
self.pad = pad
|
||||||
|
self.snapshotService = snapshotService
|
||||||
|
|
||||||
|
setupSubscribers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Publishers
|
||||||
|
extension PadDetailsView.ViewModel {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Computeds
|
||||||
|
extension PadDetailsView.ViewModel {
|
||||||
|
|
||||||
|
// TODO: Replace with MapKit Snapshot
|
||||||
|
var mapImageName: String {
|
||||||
|
"CanaveralMapSample"
|
||||||
|
}
|
||||||
|
|
||||||
|
var padNameText: String {
|
||||||
|
pad.name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var latitudeText: String {
|
||||||
|
NumberFormatters.padCoordinateDisplay.string(for: pad.latitude) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var longitudeText: String {
|
||||||
|
NumberFormatters.padCoordinateDisplay.string(for: pad.longitude) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var wikipediaURL: URL? {
|
||||||
|
pad.wikiURL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var webLinkData: [(hostName: String, url: URL)] {
|
||||||
|
([wikipediaURL] + (pad.infoURLs ?? [URL?]()))
|
||||||
|
.compactMap { url in
|
||||||
|
guard
|
||||||
|
let url = url,
|
||||||
|
let hostName = url.host
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return (hostName: strippedHostName(from: hostName), url: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
extension PadDetailsView.ViewModel {
|
||||||
|
|
||||||
|
func takeMapSnapshot(with size: CGSize) {
|
||||||
|
snapshotService
|
||||||
|
.takeSnapshot(with: size, at: pad.coordinate)
|
||||||
|
.assertNoFailure()
|
||||||
|
.print("takeMapSnapshot")
|
||||||
|
.map(\.image)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink(
|
||||||
|
receiveValue: { self.mapSnapshotImage = $0 }
|
||||||
|
)
|
||||||
|
.store(in: &subscriptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Private Helpers
|
||||||
|
private extension PadDetailsView.ViewModel {
|
||||||
|
|
||||||
|
func strippedHostName(from hostNameString: String) -> String {
|
||||||
|
hostNameString
|
||||||
|
.replacingOccurrences(of: "www.", with: "")
|
||||||
|
.replacingOccurrences(of: "\\.(.*)", with: "", options: .regularExpression)
|
||||||
|
.capitalized
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func setupSubscribers() {
|
||||||
|
|
||||||
|
// snapshotImagePublisher
|
||||||
|
// .receive(on: DispatchQueue.main)
|
||||||
|
// .sink(
|
||||||
|
// receiveValue: { self.mapSnapshotImage = $0 }
|
||||||
|
// )
|
||||||
|
// .store(in: &subscriptions)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
//
|
||||||
|
// PadDetailsView.swift
|
||||||
|
// PadFinder
|
||||||
|
//
|
||||||
|
// Created by CypherPoet on 1/27/20.
|
||||||
|
// ✌️
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MapKit
|
||||||
|
import CypherPoetSwiftUIKit
|
||||||
|
|
||||||
|
|
||||||
|
struct PadDetailsView {
|
||||||
|
@ObservedObject var viewModel: ViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - View
|
||||||
|
extension PadDetailsView: View {
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
VStack {
|
||||||
|
if self.viewModel.mapSnapshotImage != nil {
|
||||||
|
Image(uiImage: self.viewModel.mapSnapshotImage!)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
|
self.coordinateHeader
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
if self.viewModel.webLinkData.isEmpty == false {
|
||||||
|
self.linksSection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
self.viewModel.takeMapSnapshot(
|
||||||
|
with: CGSize(
|
||||||
|
width: UIScreen.main.bounds.width,
|
||||||
|
height: UIScreen.main.bounds.width * 0.75
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.embedInScrollView(axes: .vertical)
|
||||||
|
}
|
||||||
|
.navigationBarTitle(Text(viewModel.padNameText), displayMode: .inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Computeds
|
||||||
|
extension PadDetailsView {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - View Variables
|
||||||
|
extension PadDetailsView {
|
||||||
|
|
||||||
|
private var coordinateHeader: some View {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
Image(systemName: "mappin")
|
||||||
|
.padding()
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(Color.red)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
Group {
|
||||||
|
Text("Lat: ").fontWeight(.bold)
|
||||||
|
+ Text(self.viewModel.latitudeText)
|
||||||
|
|
||||||
|
Text("Lon: ").fontWeight(.bold)
|
||||||
|
+ Text(self.viewModel.longitudeText)
|
||||||
|
}
|
||||||
|
.embedInCompactableStack()
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var linksSection: some View {
|
||||||
|
Section(
|
||||||
|
header: Text("Links").font(.headline)
|
||||||
|
) {
|
||||||
|
List(viewModel.webLinkData, id: \.0) { linkItem in
|
||||||
|
Button(action: {
|
||||||
|
UIApplication.shared.open(linkItem.url)
|
||||||
|
}) {
|
||||||
|
Text(linkItem.hostName)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Private Helpers
|
||||||
|
private extension PadDetailsView {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Preview
|
||||||
|
|
||||||
|
|
||||||
|
struct PadDetailsView_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
let snapshotOptions = PreviewData.Pads.pad1.baseSnapshotOptions
|
||||||
|
|
||||||
|
return NavigationView {
|
||||||
|
PadDetailsView(
|
||||||
|
viewModel: .init(
|
||||||
|
pad: PreviewData.Pads.pad1,
|
||||||
|
snapshotService: MapSnapshottingService(
|
||||||
|
snapshotOptions: snapshotOptions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import MapKit
|
||||||
|
|
||||||
|
|
||||||
struct PadsListContainerView {
|
struct PadsListContainerView {
|
||||||
|
@ -19,8 +20,11 @@ extension PadsListContainerView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
PadsListView(viewModel: .init(padsState: padsState))
|
PadsListView(
|
||||||
.navigationBarTitle("Launch Pads")
|
viewModel: .init(padsState: padsState),
|
||||||
|
buildDestination: buildDestination(forPad:)
|
||||||
|
)
|
||||||
|
.navigationBarTitle("Launch Pads")
|
||||||
|
|
||||||
WelcomeView()
|
WelcomeView()
|
||||||
}
|
}
|
||||||
|
@ -47,21 +51,60 @@ private extension PadsListContainerView {
|
||||||
func fetchPads() {
|
func fetchPads() {
|
||||||
store.send(PadsSideEffect.fetchPads)
|
store.send(PadsSideEffect.fetchPads)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// func makeSnapshotter(for pad: Pad) -> MKMapSnapshotter {
|
||||||
|
// let snapshotOptions = pad.baseSnapshotOptions
|
||||||
|
//
|
||||||
|
// snapshotOptions.size = CGSize(width: 200, height: 200)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return MKMapSnapshotter(options: snapshotOptions)
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
func makeSnapshotOptions(for pad: Pad) -> MKMapSnapshotter.Options {
|
||||||
|
let snapshotOptions = pad.baseSnapshotOptions
|
||||||
|
|
||||||
|
return snapshotOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// func makeSnapshotService(for pad: Pad) -> MKMapSnapshotter {
|
||||||
|
// let service = MapSnapshottingService.shared
|
||||||
|
// let snapshotOptions = pad.baseSnapshotOptions
|
||||||
|
//
|
||||||
|
// snapshotOptions.size = CGSize(width: 300, height: 300)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return MKMapSnapshotter(options: snapshotOptions)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
func buildDestination(forPad pad: Pad) -> some View {
|
||||||
|
PadDetailsView(
|
||||||
|
viewModel: .init(
|
||||||
|
pad: pad,
|
||||||
|
snapshotService: MapSnapshottingService(
|
||||||
|
// snapshotter: makeSnapshotter(for: pad)
|
||||||
|
snapshotOptions: makeSnapshotOptions(for: pad)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Preview
|
// MARK: - Preview
|
||||||
struct PadsListContainerView_Previews: PreviewProvider {
|
struct PadsListContainerView_Previews: PreviewProvider {
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let store = PreviewData.AppStores.withPads
|
let store = PreviewData.AppStores.withPads
|
||||||
|
|
||||||
return PadsListContainerView(
|
return PadsListContainerView()
|
||||||
// viewModel: .init(
|
.environmentObject(store)
|
||||||
// padsState: store.state.padsState
|
|
||||||
// )
|
|
||||||
)
|
|
||||||
.environmentObject(store)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,11 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
struct PadsListView {
|
struct PadsListView<Destination: View> {
|
||||||
@EnvironmentObject private var store: AppStore
|
@EnvironmentObject private var store: AppStore
|
||||||
|
|
||||||
@ObservedObject var viewModel: ViewModel
|
@ObservedObject var viewModel: ViewModel
|
||||||
|
let buildDestination: ((Pad) -> Destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ extension PadsListView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(viewModel.pads) { pad in
|
List(viewModel.pads) { pad in
|
||||||
NavigationLink(destination: Text(pad.name)) {
|
NavigationLink(destination: self.buildDestination(pad)) {
|
||||||
HStack {
|
HStack {
|
||||||
pad.padType.listItemImage
|
pad.padType.listItemImage
|
||||||
|
|
||||||
|
@ -64,7 +65,8 @@ struct PadsListView_Previews: PreviewProvider {
|
||||||
let store = PreviewData.AppStores.withPads
|
let store = PreviewData.AppStores.withPads
|
||||||
|
|
||||||
return PadsListView(
|
return PadsListView(
|
||||||
viewModel: .init(padsState: store.state.padsState)
|
viewModel: .init(padsState: store.state.padsState),
|
||||||
|
buildDestination: { _ in EmptyView() }
|
||||||
)
|
)
|
||||||
.environmentObject(store)
|
.environmentObject(store)
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
Loading…
Reference in New Issue