diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj index a4b3dd1..46c0a1f 100644 --- a/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/project.pbxproj @@ -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 = ""; }; F32ED61923DF91C1006A5195 /* Pad+PadType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+PadType.swift"; sourceTree = ""; }; + F32ED61C23DF9B46006A5195 /* PadDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadDetailsView.swift; sourceTree = ""; }; + F32ED61E23DF9B7C006A5195 /* PadDetailsView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PadDetailsView+ViewModel.swift"; sourceTree = ""; }; + F32ED62123DFB836006A5195 /* NumberFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatters.swift; sourceTree = ""; }; + F32ED62323DFC7D5006A5195 /* Pad+SnapshotUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+SnapshotUtils.swift"; sourceTree = ""; }; + F32ED62523E0D525006A5195 /* Pad+Computeds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pad+Computeds.swift"; sourceTree = ""; }; + F32ED62823E0F0C6006A5195 /* MapSnapshottingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshottingService.swift; sourceTree = ""; }; + F32ED62A23E0F0F1006A5195 /* MapSnapshotServicing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshotServicing.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 = ""; }; @@ -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 = ""; + }; + F32ED62023DFB7E9006A5195 /* Formatters */ = { + isa = PBXGroup; + children = ( + F32ED62123DFB836006A5195 /* NumberFormatters.swift */, + ); + path = Formatters; + sourceTree = ""; + }; + F32ED62723E0F0B8006A5195 /* Services */ = { + isa = PBXGroup; + children = ( + F32ED62823E0F0C6006A5195 /* MapSnapshottingService.swift */, + F32ED62A23E0F0F1006A5195 /* MapSnapshotServicing.swift */, + ); + path = Services; + sourceTree = ""; + }; F331C44F23DDB0AE0061925E = { isa = PBXGroup; children = ( @@ -126,6 +166,8 @@ F331C47123DDB0FF0061925E /* Reusables */ = { isa = PBXGroup; children = ( + F32ED62723E0F0B8006A5195 /* Services */, + F32ED62023DFB7E9006A5195 /* Formatters */, ); path = Reusables; sourceTree = ""; @@ -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; }; 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 index b43736d..c00db16 100644 --- 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 @@ -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" } } ] diff --git a/day-096/Projects/PadFinder/PadFinder.xcodeproj/xcshareddata/xcschemes/PadFinder.xcscheme b/day-096/Projects/PadFinder/PadFinder.xcodeproj/xcshareddata/xcschemes/PadFinder.xcscheme new file mode 100644 index 0000000..898cc46 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder.xcodeproj/xcshareddata/xcschemes/PadFinder.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+Computeds.swift b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+Computeds.swift new file mode 100644 index 0000000..f10cd41 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+Computeds.swift @@ -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) + } +} + diff --git a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+SnapshotUtils.swift b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+SnapshotUtils.swift new file mode 100644 index 0000000..53e09a0 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad+SnapshotUtils.swift @@ -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 + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift index 1028b6f..a5718bc 100644 --- a/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift +++ b/day-096/Projects/PadFinder/PadFinder/Data/Models/Pad/Pad.swift @@ -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) } } 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 index 16cdc11..e546fdf 100644 --- 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 @@ -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: "") diff --git a/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/CanaveralMapSample.png b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/CanaveralMapSample.png new file mode 100644 index 0000000..8ecefbf Binary files /dev/null and b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/CanaveralMapSample.png differ diff --git a/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/Contents.json b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/Contents.json new file mode 100644 index 0000000..275c3fc --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Resources/Assets.xcassets/CanaveralMapSample.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/day-096/Projects/PadFinder/PadFinder/Reusables/Formatters/NumberFormatters.swift b/day-096/Projects/PadFinder/PadFinder/Reusables/Formatters/NumberFormatters.swift new file mode 100644 index 0000000..00b5fed --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Reusables/Formatters/NumberFormatters.swift @@ -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 + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshotServicing.swift b/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshotServicing.swift new file mode 100644 index 0000000..b657121 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshotServicing.swift @@ -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 +} + + +extension MapSnapshotServicing { + + func takeSnapshot( + with size: CGSize, + at coordinate: CLLocationCoordinate2D, + latitudeSpan: CLLocationDegrees = 0.15, + longitudeSpan: CLLocationDegrees = 0.15 + ) -> Future { + 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)) + } + } + } +} + diff --git a/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshottingService.swift b/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshottingService.swift new file mode 100644 index 0000000..cb9b5e1 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Reusables/Services/MapSnapshottingService.swift @@ -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 + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView+ViewModel.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView+ViewModel.swift new file mode 100644 index 0000000..8349595 --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView+ViewModel.swift @@ -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() + + 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) + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView.swift new file mode 100644 index 0000000..fe0da0d --- /dev/null +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/Pad Details/PadDetailsView.swift @@ -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 + ) + ) + ) + } + } +} diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift index 1511074..62bcedf 100644 --- a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListContainerView.swift @@ -7,6 +7,7 @@ // import SwiftUI +import MapKit struct PadsListContainerView { @@ -19,8 +20,11 @@ extension PadsListContainerView: View { var body: some View { NavigationView { - PadsListView(viewModel: .init(padsState: padsState)) - .navigationBarTitle("Launch Pads") + PadsListView( + viewModel: .init(padsState: padsState), + buildDestination: buildDestination(forPad:) + ) + .navigationBarTitle("Launch Pads") WelcomeView() } @@ -47,21 +51,60 @@ 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 { static var previews: some View { let store = PreviewData.AppStores.withPads - return PadsListContainerView( -// viewModel: .init( -// padsState: store.state.padsState -// ) - ) - .environmentObject(store) + return PadsListContainerView() + .environmentObject(store) } } diff --git a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift index 0744f98..25b2174 100644 --- a/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift +++ b/day-096/Projects/PadFinder/PadFinder/Scenes/Pads/PadsListView.swift @@ -9,10 +9,11 @@ import SwiftUI -struct PadsListView { +struct PadsListView { @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) } diff --git a/day-096/Projects/PadFinder/Resources/AssetImages/CanaveralMapSample.png b/day-096/Projects/PadFinder/Resources/AssetImages/CanaveralMapSample.png new file mode 100644 index 0000000..8ecefbf Binary files /dev/null and b/day-096/Projects/PadFinder/Resources/AssetImages/CanaveralMapSample.png differ