Begin implementing Pad details view.
This commit is contained in:
parent
8faf051801
commit
922337f7e6
|
@ -9,6 +9,13 @@
|
|||
/* 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 */; };
|
||||
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 */; };
|
||||
F331C45E23DDB0AE0061925E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331C45D23DDB0AE0061925E /* SceneDelegate.swift */; };
|
||||
F331C46223DDB0B00061925E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F331C46123DDB0B00061925E /* Assets.xcassets */; };
|
||||
|
@ -33,6 +40,13 @@
|
|||
/* Begin PBXFileReference section */
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
@ -67,6 +81,32 @@
|
|||
/* End PBXFrameworksBuildPhase 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 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -126,6 +166,8 @@
|
|||
F331C47123DDB0FF0061925E /* Reusables */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F32ED62723E0F0B8006A5195 /* Services */,
|
||||
F32ED62023DFB7E9006A5195 /* Formatters */,
|
||||
);
|
||||
path = Reusables;
|
||||
sourceTree = "<group>";
|
||||
|
@ -164,6 +206,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F331C47B23DDB1710061925E /* PadsListContainerView.swift */,
|
||||
F32ED61B23DF9B36006A5195 /* Pad Details */,
|
||||
F32ED61723DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift */,
|
||||
F331C49B23DE18650061925E /* PadsListView.swift */,
|
||||
F331C49D23DE187A0061925E /* PadsListView+ViewModel.swift */,
|
||||
|
@ -192,6 +235,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F331C48823DDDF0E0061925E /* Pad.swift */,
|
||||
F32ED62323DFC7D5006A5195 /* Pad+SnapshotUtils.swift */,
|
||||
F32ED62523E0D525006A5195 /* Pad+Computeds.swift */,
|
||||
F32ED61923DF91C1006A5195 /* Pad+PadType.swift */,
|
||||
);
|
||||
path = Pad;
|
||||
|
@ -287,11 +332,17 @@
|
|||
files = (
|
||||
F331C47C23DDB1710061925E /* PadsListContainerView.swift in Sources */,
|
||||
F331C48523DDDE710061925E /* AppState.swift in Sources */,
|
||||
F32ED62923E0F0C6006A5195 /* MapSnapshottingService.swift in Sources */,
|
||||
F32ED61F23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift in Sources */,
|
||||
F331C49C23DE18650061925E /* PadsListView.swift in Sources */,
|
||||
F32ED61823DF69E6006A5195 /* PadsListContainerView+WelcomeView.swift in Sources */,
|
||||
F331C48923DDDF0E0061925E /* Pad.swift in Sources */,
|
||||
F331C49623DE0FED0061925E /* PreviewData.swift in Sources */,
|
||||
F32ED62423DFC7D5006A5195 /* Pad+SnapshotUtils.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 */,
|
||||
F331C45C23DDB0AE0061925E /* AppDelegate.swift in Sources */,
|
||||
F331C48323DDDE570061925E /* CurrentApplication.swift in Sources */,
|
||||
|
@ -301,6 +352,7 @@
|
|||
F32ED61A23DF91C1006A5195 /* Pad+PadType.swift in Sources */,
|
||||
F331C49823DE10010061925E /* PreviewData+AppStore.swift in Sources */,
|
||||
F331C48723DDDEC30061925E /* PadsState.swift in Sources */,
|
||||
F32ED61D23DF9B46006A5195 /* PadDetailsView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
"repositoryURL": "https://github.com/CypherPoet/CypherPoetSwiftUIKit.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "65d4268cddbedfeabe44cd625b35bd1812a37587",
|
||||
"version": "0.0.33"
|
||||
"revision": "dca4353f34bdf5a622fd5fc7c31dc72ad377194a",
|
||||
"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 padType: PadType
|
||||
var mapURL: URL
|
||||
var wikiURL: URL?
|
||||
var latitude: CLLocationDegrees
|
||||
var longitude: CLLocationDegrees
|
||||
var isRetired: Bool
|
||||
var infoURLs: [URL]
|
||||
var infoURLs: [URL]?
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,6 +43,7 @@ extension Pad: Decodable {
|
|||
case name = "name"
|
||||
case padType = "padType"
|
||||
case mapURL = "mapURL"
|
||||
case wikiURL = "wikiURL"
|
||||
case latitude = "latitude"
|
||||
case longitude = "longitude"
|
||||
case isRetired = "retired"
|
||||
|
@ -64,6 +66,14 @@ extension Pad: Decodable {
|
|||
// 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)
|
||||
latitude = CLLocationDegrees(latitudeString) ?? 0.0
|
||||
|
||||
|
@ -73,7 +83,7 @@ extension Pad: Decodable {
|
|||
let isRetiredInt = try container.decode(Int.self, forKey: .isRetired)
|
||||
|
||||
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,
|
||||
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: "")
|
||||
|
|
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 MapKit
|
||||
|
||||
|
||||
struct PadsListContainerView {
|
||||
|
@ -19,7 +20,10 @@ extension PadsListContainerView: View {
|
|||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
PadsListView(viewModel: .init(padsState: padsState))
|
||||
PadsListView(
|
||||
viewModel: .init(padsState: padsState),
|
||||
buildDestination: buildDestination(forPad:)
|
||||
)
|
||||
.navigationBarTitle("Launch Pads")
|
||||
|
||||
WelcomeView()
|
||||
|
@ -47,9 +51,52 @@ private extension PadsListContainerView {
|
|||
func 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
|
||||
struct PadsListContainerView_Previews: PreviewProvider {
|
||||
|
@ -57,11 +104,7 @@ struct PadsListContainerView_Previews: PreviewProvider {
|
|||
static var previews: some View {
|
||||
let store = PreviewData.AppStores.withPads
|
||||
|
||||
return PadsListContainerView(
|
||||
// viewModel: .init(
|
||||
// padsState: store.state.padsState
|
||||
// )
|
||||
)
|
||||
return PadsListContainerView()
|
||||
.environmentObject(store)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
import SwiftUI
|
||||
|
||||
|
||||
struct PadsListView {
|
||||
struct PadsListView<Destination: View> {
|
||||
@EnvironmentObject private var store: AppStore
|
||||
|
||||
@ObservedObject var viewModel: ViewModel
|
||||
let buildDestination: ((Pad) -> Destination)
|
||||
}
|
||||
|
||||
|
||||
|
@ -21,7 +22,7 @@ extension PadsListView: View {
|
|||
|
||||
var body: some View {
|
||||
List(viewModel.pads) { pad in
|
||||
NavigationLink(destination: Text(pad.name)) {
|
||||
NavigationLink(destination: self.buildDestination(pad)) {
|
||||
HStack {
|
||||
pad.padType.listItemImage
|
||||
|
||||
|
@ -64,7 +65,8 @@ struct PadsListView_Previews: PreviewProvider {
|
|||
let store = PreviewData.AppStores.withPads
|
||||
|
||||
return PadsListView(
|
||||
viewModel: .init(padsState: store.state.padsState)
|
||||
viewModel: .init(padsState: store.state.padsState),
|
||||
buildDestination: { _ in EmptyView() }
|
||||
)
|
||||
.environmentObject(store)
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
Loading…
Reference in New Issue