Showing with 204 additions and 290 deletions.
  1. 0 App/{Models → Extensions}/CLLocationCoordinate2DPortland.swift
  2. +0 −5 App/Extensions/URL+Example.swift
  3. +0 −4 App/Models/Dimension.swift
  4. +1 −1 App/Supporting Files/Info.plist
  5. +1 −0 App/View Models/TreeViewModel.swift
  6. +2 −0 App/View Models/VisitableTreeViewModel.swift
  7. +3 −0 App/Views/App.swift
  8. +1 −3 App/Views/HomeView.swift
  9. +0 −12 App/Views/SafariView.swift
  10. +1 −0 App/Views/ToggleVisitedTreeButton.swift
  11. +4 −2 App/Views/TreeDetailView.swift
  12. +0 −32 App/Views/TreeDimensionsView.swift
  13. +1 −0 App/Views/TreeListItem.swift
  14. +1 −0 App/Views/TreeListView.swift
  15. +1 −0 App/Views/TreeLocationRow.swift
  16. +1 −0 App/Views/TreeMapDetailView.swift
  17. +2 −0 App/Views/TreeMapView.swift
  18. +11 −2 Library/Package.swift
  19. 0 {App → Library/Sources/Model}/Extensions/OpenDataTree+Tree.swift
  20. +1 −1 {App → Library/Sources/Model}/Extensions/Tree+Coordinate.swift
  21. 0 Library/Sources/{Library → Model}/Library.swift
  22. +1 −1 {App → Library/Sources/Model}/Models/APIError.swift
  23. +3 −3 {App → Library/Sources/Model}/Models/Environment.swift
  24. +5 −3 {App → Library/Sources/Model}/Models/LocationManager.swift
  25. 0 {App → Library/Sources/Model}/Models/OpenDataEndpoint.swift
  26. 0 {App → Library/Sources/Model}/Models/OpenDataTree.swift
  27. 0 {App → Library/Sources/Model}/Models/Persistence.swift
  28. +1 −1 {App → Library/Sources/Model}/Models/RequestBuilder.swift
  29. +15 −15 {App → Library/Sources/Model}/Models/Tree.swift
  30. +8 −7 {App/View Models → Library/Sources/Model/Models}/TreeAnnotation.swift
  31. 0 {App → Library/Sources/Model}/Models/TreeListAPIResponse.swift
  32. +18 −17 {App → Library/Sources/Model}/Models/TreeStore.swift
  33. +2 −2 {App → Library/Sources/Model}/Models/WikipediaURL.swift
  34. +1 −1 {App → Library/Sources/Model}/Services/APIService.swift
  35. 0 {App → Library/Sources/Model}/Services/APISession.swift
  36. 0 {App → Library/Sources/Model}/Services/LocalSession.swift
  37. 0 {App → Library/Sources/Model}/Services/OpenDataService.swift
  38. 0 {App/Protocols → Library/Sources/Toolbox}/Annotation.swift
  39. +9 −0 Library/Sources/Toolbox/Dimension.swift
  40. +5 −0 Library/Sources/Toolbox/URL+Example.swift
  41. +10 −5 {App/Views → Library/Sources/ViewToolbox}/AttributeRow.swift
  42. +5 −0 Library/Sources/ViewToolbox/Colors.swift
  43. +11 −6 {App/Views → Library/Sources/ViewToolbox}/DimensionGroup.swift
  44. +1 −1 {App/Views → Library/Sources/ViewToolbox}/DimensionView.swift
  45. +40 −0 Library/Sources/ViewToolbox/DimensionsView.swift
  46. +3 −2 {App/Models → Library/Sources/ViewToolbox}/MapView.Coordinator.swift
  47. +1 −0 {App/Views → Library/Sources/ViewToolbox}/MapView.swift
  48. +13 −7 {App/Views → Library/Sources/ViewToolbox}/NameView.swift
  49. 0 {App/Views → Library/Sources/ViewToolbox}/PinAnnotationView.swift
  50. 0 {App/Views → Library/Sources/ViewToolbox}/SafariButton.swift
  51. +16 −0 Library/Sources/ViewToolbox/SafariView.swift
  52. +1 −1 {App/Extensions → Library/Sources/ViewToolbox}/View+Preview.swift
  53. +4 −156 portland-heritage-trees.xcodeproj/project.pbxproj

This file was deleted.

This file was deleted.

@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>6</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
@@ -1,4 +1,5 @@
import Foundation
import Model

class TreeViewModel: ObservableObject, Identifiable {
let tree: Tree
@@ -1,3 +1,5 @@
import Model

class VisitableTreeViewModel: TreeViewModel {
let isVisited: Bool
var visitedButtonImageName: String { isVisited ? "checkmark.circle.fill" : "checkmark.circle" }
@@ -1,4 +1,6 @@
import Model
import SwiftUI
import ViewToolbox

@main
struct Portland_Heritage_TreesApp: App {
@@ -8,6 +10,7 @@ struct Portland_Heritage_TreesApp: App {
WindowGroup {
HomeView()
.environmentObject(environment.store)
.onAppear() { Colors.secondaryText = .mutedText }
}
}
}
@@ -1,3 +1,4 @@
import Model
import SwiftUI

struct HomeView: View {
@@ -17,9 +18,6 @@ struct HomeView: View {
}
.navigationBarTitle("Heritage Trees", displayMode: .inline)
}
.onAppear {
store.getTrees()
}
}

private var mapButton: some View {

This file was deleted.

@@ -1,3 +1,4 @@
import Model
import SwiftUI

struct ToggleVisitedTreeButton: View {
@@ -1,4 +1,6 @@
import Model
import SwiftUI
import ViewToolbox

struct TreeDetailView: View {
let tree: Tree
@@ -18,7 +20,7 @@ struct TreeDetailView: View {
.padding(.vertical, 8)
TreeLocationRow(tree: tree)
.padding(.vertical, 8)
TreeDimensionsView(height: viewModel.height, spread: viewModel.spread, diameter: viewModel.diameter, circumference: viewModel.circumference)
DimensionsView(height: viewModel.height, spread: viewModel.spread, diameter: viewModel.diameter, circumference: viewModel.circumference)
.padding(.vertical, 8)
AttributeRow(name: "Notes", value: viewModel.notes)
.padding(.vertical, 8)
@@ -27,7 +29,7 @@ struct TreeDetailView: View {
.padding(.bottom, 64)
}
}

VStack {
Spacer()
ToggleVisitedTreeButton(tree: tree)

This file was deleted.

@@ -1,3 +1,4 @@
import Model
import SwiftUI

struct TreeListItem: View {
@@ -1,3 +1,4 @@
import Model
import SwiftUI

struct TreeListView: View {
@@ -1,4 +1,5 @@
import MapKit
import Model
import SwiftUI

struct TreeLocationRow: View {
@@ -1,4 +1,5 @@
import MapKit
import Model
import SwiftUI

struct TreeMapDetailView: View {
@@ -1,5 +1,7 @@
import MapKit
import Model
import SwiftUI
import ViewToolbox

struct TreeMapView: View {
@EnvironmentObject private var store: TreeStore
@@ -4,16 +4,25 @@ import PackageDescription

let package = Package(
name: "Library",
platforms: [.iOS(.v14)],
products: [
.library(
name: "Library",
targets: ["Library"]
targets: ["Model", "Toolbox", "ViewToolbox"]
),
],
targets: [
.target(
name: "Library",
name: "Model",
dependencies: ["Toolbox"]
),
.target(
name: "Toolbox",
dependencies: []
),
.target(
name: "ViewToolbox",
dependencies: ["Toolbox"]
),
]
)
File renamed without changes.
@@ -1,7 +1,7 @@
import MapKit

extension Tree {
var coordinate: CLLocationCoordinate2D? {
public var coordinate: CLLocationCoordinate2D? {
guard
let latitude = latitude,
let longitude = longitude
File renamed without changes.
@@ -1,3 +1,3 @@
enum APIError: Error {
public enum APIError: Error {
case decoding, http(Int), unknown
}
@@ -1,9 +1,9 @@
enum Environment {
public enum Environment {
case local, remote
}

extension Environment {
var apiService: APIService {
public var apiService: APIService {
switch self {
case .local:
return LocalSession()
@@ -14,7 +14,7 @@ extension Environment {
}

extension Environment {
var store: TreeStore {
public var store: TreeStore {
TreeStore(apiService: apiService)
}
}
@@ -1,14 +1,16 @@
import CoreLocation

struct LocationManager {
public struct LocationManager {
private let locationManager = CLLocationManager()

func requestAuthorization() {
public init() {}

public func requestAuthorization() {
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}

func rerequestAuthorization() {
public func rerequestAuthorization() {
if locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.requestWhenInUseAuthorization()
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
import Foundation

protocol RequestBuilder {
public protocol RequestBuilder {
var urlRequest: URLRequest { get }
}
@@ -1,25 +1,25 @@
import Foundation

struct Tree: Identifiable {
let address: String?
let circumference: Float?
let commonName: String
let diameter: Int?
let fact: String?
let height: Int
let id: Int
let latitude: Double?
let longitude: Double?
let neighborhood: String?
let notes: String?
let scientificName: String
let spread: Int?
public struct Tree: Identifiable {
public let address: String?
public let circumference: Float?
public let commonName: String
public let diameter: Int?
public let fact: String?
public let height: Int
public let id: Int
public let latitude: Double?
public let longitude: Double?
public let neighborhood: String?
public let notes: String?
public let scientificName: String
public let spread: Int?
}

// MARK: Preview Content
extension Tree {
static var preview: Self {
public static var preview: Self {
Self(
address: "1111 SW 10th AVE",
circumference: 12.8,
@@ -1,9 +1,10 @@
import MapKit
import SwiftUI
import Toolbox

class TreeAnnotation: NSObject {
let coordinate: CLLocationCoordinate2D
let tree: Tree
public class TreeAnnotation: NSObject {
public let coordinate: CLLocationCoordinate2D
public let tree: Tree
let isVisited: Bool

init?(tree: Tree, isVisited: Bool) {
@@ -15,13 +16,13 @@ class TreeAnnotation: NSObject {
}

extension TreeAnnotation: MKAnnotation {
var title: String? { "Tree #\(tree.id)" }
var subtitle: String? { tree.commonName }
public var title: String? { "Tree #\(tree.id)" }
public var subtitle: String? { tree.commonName }
}

extension TreeAnnotation: Annotation {
var identifier: Int { tree.id }
var tintColor: UIColor { isVisited ? color.withAlphaComponent(0.5) : color }
public var identifier: Int { tree.id }
public var tintColor: UIColor { isVisited ? color.withAlphaComponent(0.5) : color }

private var color: UIColor { UIColor(Color.accentColor) }
}
File renamed without changes.
@@ -1,25 +1,37 @@
import Combine

class TreeStore: ObservableObject, OpenDataService {
@Published private(set) var treeAnnotations = [TreeAnnotation]()
public class TreeStore: ObservableObject, OpenDataService {
@Published public private(set) var treeAnnotations = [TreeAnnotation]()
let apiService: APIService

@Published private(set) var isVisitedStatuses: [Int: Bool] {
@Published public private(set) var trees = [Tree]() {
didSet { setTreeAnnotations() }
}

@Published private(set) var trees = [Tree]() {
@Published private(set) var isVisitedStatuses: [Int: Bool] {
didSet { setTreeAnnotations() }
}

private var cancellables = Set<AnyCancellable>()

init(apiService: APIService) {
public init(apiService: APIService) {
self.apiService = apiService
self.isVisitedStatuses = Persistence.isVisitedStatuses
getTrees()
}

public func isVisited(tree: Tree) -> Bool {
isVisitedStatuses[tree.id] ?? false
}

func getTrees() {
public func toggleTreeIsVisited(_ tree: Tree) {
var statuses = isVisitedStatuses
statuses[tree.id] = !(statuses[tree.id] ?? false)
isVisitedStatuses = statuses
Persistence.isVisitedStatuses = isVisitedStatuses
}

private func getTrees() {
getTrees()
.sink(receiveCompletion: { result in
switch result {
@@ -35,17 +47,6 @@ class TreeStore: ObservableObject, OpenDataService {
.store(in: &cancellables)
}

func isVisited(tree: Tree) -> Bool {
isVisitedStatuses[tree.id] ?? false
}

func toggleTreeIsVisited(_ tree: Tree) {
var statuses = isVisitedStatuses
statuses[tree.id] = !(statuses[tree.id] ?? false)
isVisitedStatuses = statuses
Persistence.isVisitedStatuses = isVisitedStatuses
}

private func setTreeAnnotations() {
treeAnnotations = trees.compactMap { tree in
let isVisited = isVisitedStatuses[tree.id] ?? false