Skip to content

Commit

Permalink
fix: Toggle location saving and improve location acquisition reliability
Browse files Browse the repository at this point in the history
  • Loading branch information
jbmorley committed Apr 24, 2024
1 parent 5f63da6 commit 4409913
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 61 deletions.
18 changes: 9 additions & 9 deletions Thoughts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
D80E08A62B6761370023DD4F /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80E08A52B6761370023DD4F /* Document.swift */; };
D83C2F882B857B8C00CE60F7 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D83C2F872B857B8C00CE60F7 /* URL.swift */; };
D84010C52BD74C760049715E /* yams-license in Resources */ = {isa = PBXBuildFile; fileRef = D84010C42BD74C760049715E /* yams-license */; };
D840967F2BD73CBE00926C85 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = D840967E2BD73CBE00926C85 /* Location.swift */; };
D840967F2BD73CBE00926C85 /* LocationDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = D840967E2BD73CBE00926C85 /* LocationDetails.swift */; };
D850C2D42B69D85400338546 /* KeyedDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D850C2D32B69D85400338546 /* KeyedDefaults.swift */; };
D850C2D62B69D8D500338546 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D850C2D52B69D8D500338546 /* ContentView.swift */; };
D852AF022B6027CA00B77A3D /* ThoughtsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852AF012B6027CA00B77A3D /* ThoughtsApp.swift */; };
Expand All @@ -23,7 +23,6 @@
D852AF642B604AF500B77A3D /* ApplicationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852AF632B604AF500B77A3D /* ApplicationModel.swift */; };
D852AF662B604B1200B77A3D /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852AF652B604B1200B77A3D /* ComposeView.swift */; };
D852AF692B604B3E00B77A3D /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852AF682B604B3E00B77A3D /* MainMenu.swift */; };
D852AF6B2B604B5100B77A3D /* ComposeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852AF6A2B604B5100B77A3D /* ComposeModel.swift */; };
D892249A2BD70CC300DA0932 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = D89224992BD70CC300DA0932 /* Yams */; };
D892249C2BD735A100DA0932 /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D892249B2BD735A100DA0932 /* Metadata.swift */; };
D892249E2BD735BE00DA0932 /* ThoughtsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D892249D2BD735BE00DA0932 /* ThoughtsError.swift */; };
Expand All @@ -32,6 +31,7 @@
D89224A42BD7368F00DA0932 /* TimeZoneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89224A32BD7368F00DA0932 /* TimeZoneTests.swift */; };
D89224A72BD736B900DA0932 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89224A62BD736B900DA0932 /* Date.swift */; };
D89224A92BD736DF00DA0932 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89224A82BD736DF00DA0932 /* TimeZone.swift */; };
D8DFFCC82BD82DE900C9532A /* CLAuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DFFCC72BD82DE900C9532A /* CLAuthorizationStatus.swift */; };
D8F06D4B2B67275B0039AEF4 /* Diligence in Frameworks */ = {isa = PBXBuildFile; productRef = D8F06D4A2B67275B0039AEF4 /* Diligence */; };
D8F06D4E2B6727630039AEF4 /* Interact in Frameworks */ = {isa = PBXBuildFile; productRef = D8F06D4D2B6727630039AEF4 /* Interact */; };
D8F06D512B6731CE0039AEF4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F06D502B6731CE0039AEF4 /* SettingsView.swift */; };
Expand Down Expand Up @@ -62,7 +62,7 @@
D80E08A52B6761370023DD4F /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
D83C2F872B857B8C00CE60F7 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
D84010C42BD74C760049715E /* yams-license */ = {isa = PBXFileReference; lastKnownFileType = text; path = "yams-license"; sourceTree = "<group>"; };
D840967E2BD73CBE00926C85 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
D840967E2BD73CBE00926C85 /* LocationDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetails.swift; sourceTree = "<group>"; };
D850C2D32B69D85400338546 /* KeyedDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedDefaults.swift; sourceTree = "<group>"; };
D850C2D52B69D8D500338546 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
D852AEFE2B6027CA00B77A3D /* Thoughts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Thoughts.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -78,14 +78,14 @@
D852AF632B604AF500B77A3D /* ApplicationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationModel.swift; sourceTree = "<group>"; };
D852AF652B604B1200B77A3D /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = "<group>"; };
D852AF682B604B3E00B77A3D /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
D852AF6A2B604B5100B77A3D /* ComposeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeModel.swift; sourceTree = "<group>"; };
D892249B2BD735A100DA0932 /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = "<group>"; };
D892249D2BD735BE00DA0932 /* ThoughtsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThoughtsError.swift; sourceTree = "<group>"; };
D892249F2BD735DA00DA0932 /* RegionalDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionalDate.swift; sourceTree = "<group>"; };
D89224A12BD7365C00DA0932 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; };
D89224A32BD7368F00DA0932 /* TimeZoneTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneTests.swift; sourceTree = "<group>"; };
D89224A62BD736B900DA0932 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
D89224A82BD736DF00DA0932 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; };
D8DFFCC72BD82DE900C9532A /* CLAuthorizationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLAuthorizationStatus.swift; sourceTree = "<group>"; };
D8F06D502B6731CE0039AEF4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
D8F06D532B6733650039AEF4 /* thoughts-license */ = {isa = PBXFileReference; lastKnownFileType = text; path = "thoughts-license"; sourceTree = "<group>"; };
D8F06D552B6734540039AEF4 /* material-icons-license */ = {isa = PBXFileReference; lastKnownFileType = text; path = "material-icons-license"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -123,9 +123,9 @@
D80E089F2B67572E0023DD4F /* Model */ = {
isa = PBXGroup;
children = (
D852AF6A2B604B5100B77A3D /* ComposeModel.swift */,
D852AF632B604AF500B77A3D /* ApplicationModel.swift */,
D80E08A52B6761370023DD4F /* Document.swift */,
D840967E2BD73CBE00926C85 /* Location.swift */,
D840967E2BD73CBE00926C85 /* LocationDetails.swift */,
D892249B2BD735A100DA0932 /* Metadata.swift */,
D892249F2BD735DA00DA0932 /* RegionalDate.swift */,
D892249D2BD735BE00DA0932 /* ThoughtsError.swift */,
Expand All @@ -136,6 +136,7 @@
D80E08A22B675AD40023DD4F /* Extensions */ = {
isa = PBXGroup;
children = (
D8DFFCC72BD82DE900C9532A /* CLAuthorizationStatus.swift */,
D850C2D32B69D85400338546 /* KeyedDefaults.swift */,
D89224A12BD7365C00DA0932 /* TimeZone.swift */,
D83C2F872B857B8C00CE60F7 /* URL.swift */,
Expand Down Expand Up @@ -169,7 +170,6 @@
D852AF0A2B6027CC00B77A3D /* Thoughts.entitlements */,
D8F8D7232B68C77900464A37 /* ThoughtsRelease.entitlements */,
D80E089E2B674E900023DD4F /* Info.plist */,
D852AF632B604AF500B77A3D /* ApplicationModel.swift */,
D852AF012B6027CA00B77A3D /* ThoughtsApp.swift */,
D852AF052B6027CC00B77A3D /* Assets.xcassets */,
D80E08A22B675AD40023DD4F /* Extensions */,
Expand Down Expand Up @@ -384,7 +384,8 @@
files = (
D852AF642B604AF500B77A3D /* ApplicationModel.swift in Sources */,
D850C2D62B69D8D500338546 /* ContentView.swift in Sources */,
D840967F2BD73CBE00926C85 /* Location.swift in Sources */,
D840967F2BD73CBE00926C85 /* LocationDetails.swift in Sources */,
D8DFFCC82BD82DE900C9532A /* CLAuthorizationStatus.swift in Sources */,
D852AF692B604B3E00B77A3D /* MainMenu.swift in Sources */,
D83C2F882B857B8C00CE60F7 /* URL.swift in Sources */,
D89224A02BD735DA00DA0932 /* RegionalDate.swift in Sources */,
Expand All @@ -397,7 +398,6 @@
D80E08A12B675AAC0023DD4F /* ComposeWindow.swift in Sources */,
D89224A22BD7365C00DA0932 /* TimeZone.swift in Sources */,
D892249C2BD735A100DA0932 /* Metadata.swift in Sources */,
D852AF6B2B604B5100B77A3D /* ComposeModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Combine
import SwiftUI
import CoreLocation

class ComposeModel: ObservableObject {
extension CLAuthorizationStatus {

@Published var error: Error?

let applicationModel: ApplicationModel

private var cancellables: Set<AnyCancellable> = []

init(applicationModel: ApplicationModel) {
self.applicationModel = applicationModel
var name: String {
switch self {
case .notDetermined:
return "not determined"
case .restricted:
return "restricted"
case .denied:
return "denied"
case .authorizedAlways:
return "authorized always"
@unknown default:
return "unknown (\(self.rawValue))"
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ApplicationModel: NSObject {

enum SettingsKey: String {
case rootURL
case shouldSaveLocation
}

var rootURL: URL? {
Expand All @@ -41,6 +42,20 @@ class ApplicationModel: NSObject {
}
}

var shouldSaveLocation: Bool {
didSet {
keyedDefaults.set(shouldSaveLocation, forKey: .shouldSaveLocation)
if shouldSaveLocation {
updateUserLocation()
} else {
document.location = nil
locationManager.stopUpdatingLocation()
}
}
}

var locationRequests: [(Result<LocationDetails, Error>) -> Void] = []

var document = Document() {
didSet {
guard let url = rootURL else {
Expand All @@ -56,28 +71,29 @@ class ApplicationModel: NSObject {

let keyedDefaults = KeyedDefaults<SettingsKey>()
let locationManager = CLLocationManager()
var lastKnownLocation: Location? = nil

override init() {
rootURL = try? keyedDefaults.securityScopedURL(forKey: .rootURL)
shouldSaveLocation = keyedDefaults.bool(forKey: .shouldSaveLocation, default: false)
super.init()
locationManager.delegate = self
}

func new() {
dispatchPrecondition(condition: .onQueue(.main))
document = Document()
document.location = lastKnownLocation
updateUserLocation()
}

func userLocation() {
guard locationManager.authorizationStatus != .notDetermined else {
print("Requesting location authorization...")
locationManager.requestWhenInUseAuthorization()
return
func updateUserLocation() {
requestUserLocation { result in
switch result {
case .success(let location):
self.document.location = location
case .failure(let error):
print("Failed to fetch location with error \(error)")
}
}
print("Requsting location...")
locationManager.requestLocation()
}

func setRootURL() {
Expand All @@ -98,67 +114,76 @@ class ApplicationModel: NSObject {

extension ApplicationModel: CLLocationManagerDelegate {

func requestUserLocation(completion: @escaping (Result<LocationDetails, Error>) -> Void) {
guard shouldSaveLocation else {
return
}
locationRequests.append(completion)
guard locationManager.authorizationStatus != .notDetermined else {
print("Requesting location authorization...")
locationManager.requestWhenInUseAuthorization()
return
}
print("Requsting location...")
locationManager.requestLocation()
}

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
dispatchPrecondition(condition: .onQueue(.main))
print("Location authorization status = \(manager.authorizationStatus.name)")
guard manager.authorizationStatus == .authorized else {
return
}
print("Requsting location...")
guard locationRequests.count > 0 else {
return
}
manager.requestLocation()
}

func resolveLocation(_ location: CLLocation) async -> Location {
func resolveLocation(_ location: CLLocation) async -> LocationDetails {
let geocoder = CLGeocoder()
do {
guard let placemark = try await geocoder.reverseGeocodeLocation(location).first else {
return Location(location)
return LocationDetails(location)
}
return Location(placemark)
return LocationDetails(placemark)
} catch {
print("Failed to geocode location with error \(error).")
return Location(location)
return LocationDetails(location)
}
}

func resolveRequests(_ result: Result<LocationDetails, Error>) {
dispatchPrecondition(condition: .onQueue(.main))
for locationRequest in self.locationRequests {
locationRequest(result)
}
locationRequests = []
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("Did update locations.")
Task {
guard let location = locations.first else {
return
}
let lastKnownLocation = await resolveLocation(location)
print(lastKnownLocation)
let locationDetails = await resolveLocation(location)
await MainActor.run {
self.lastKnownLocation = lastKnownLocation
resolveRequests(.success(locationDetails))
}
}
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
print("Location manager did fail with error \(error).")
resolveRequests(.failure(error))
}

func locationManager(_ manager: CLLocationManager,
monitoringDidFailFor region: CLRegion?, withError error: any Error) {
print("Location manager monitoring did fail with error \(error).")
}

}

extension CLAuthorizationStatus {

var name: String {
switch self {
case .notDetermined:
return "not determined"
case .restricted:
return "restricted"
case .denied:
return "denied"
case .authorizedAlways:
return "authorized always"
@unknown default:
return "unknown (\(self.rawValue))"
}
resolveRequests(.failure(error))
}

}
2 changes: 1 addition & 1 deletion Thoughts/Model/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct Document {
var date: Date
var content: String
var tags: String
var location: Location? = nil
var location: LocationDetails? = nil

var isEmpty: Bool {
return content.isEmpty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import CoreLocation
import Foundation

struct Location: Codable {
struct LocationDetails: Codable {

var latitude: CLLocationDegrees?
var longitude: CLLocationDegrees?
Expand Down
2 changes: 1 addition & 1 deletion Thoughts/Model/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ struct Metadata: Codable {

let date: RegionalDate
let tags: [String]
let location: Location?
let location: LocationDetails?

}
3 changes: 3 additions & 0 deletions Thoughts/Views/ComposeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ struct ComposeView: View {
let longitude = location.longitude {
Text("\(latitude), \(longitude)")
}
} else if applicationModel.shouldSaveLocation {
ProgressView()
.controlSize(.small)
}
}
.foregroundStyle(.secondary)
Expand Down
18 changes: 15 additions & 3 deletions Thoughts/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ struct ContentView: View {

var applicationModel: ApplicationModel

var systemImage: String {
if applicationModel.shouldSaveLocation {
if applicationModel.document.location != nil {
return "location.fill"
} else {
return "location"
}
} else {
return "location.slash"
}
}

var body: some View {
HStack {
if applicationModel.rootURL != nil {
Expand All @@ -44,11 +56,11 @@ struct ContentView: View {
.toolbar {
ToolbarItem {
Button {
applicationModel.userLocation()
applicationModel.shouldSaveLocation.toggle()
} label: {
let hasLocation = applicationModel.document.location != nil
Label("Use Location", systemImage: hasLocation ? "location.fill" : "location")
.foregroundColor(.purple)
Label("Use Location", systemImage: systemImage)
.foregroundColor(hasLocation ? .purple : nil)
}
.disabled(applicationModel.rootURL == nil)
}
Expand Down

0 comments on commit 4409913

Please sign in to comment.