feat: add native local location capture (#4)

Add explicit permission handling, independent continuous and single capture,
location status and deterministic CI coverage.

Final decisions and validation:
https://github.com/jayanezj/traccar-client-macos/issues/4#issuecomment-6399189792
This commit is contained in:
jayanezj committed 2026-10-25 15:58:05 +01:00
1 parent db90ad9a0e
commit 3f351296b8
18 files changed
+1030 -38

No files matched your search

@@ -31,12 +31,12 @@ final class ConfigurationModel {
self.beforeConnectionChange = beforeConnectionChange
}
static func live() -> ConfigurationModel {
ConfigurationModel(storage: PreferencesConfigurationStorage(
preferences: .standard, connectionStorage: KeychainConnectionStorage(
service: "\(Bundle.main.bundleIdentifier ?? "com.codigojose.traccar-client-macos").configuration"
)
))
static func live(
beforeConnectionChange: @escaping (ReceiverConnection, ReceiverConnection) throws -> Void = { _, _ in }
) -> ConfigurationModel {
ConfigurationModel(
storage: PreferencesConfigurationStorage.live(), beforeConnectionChange: beforeConnectionChange
)
}
func loadIfNeeded() {
@@ -83,7 +83,7 @@ final class ConfigurationModel {
self.saved = candidate
draft = candidate
errorMessage = nil
showStatus("Configuration saved locally. Tracking remains paused.")
showStatus("Configuration saved locally. Saving does not start capture or send positions.")
return true
} catch {
errorMessage = message(for: error)
@@ -9,6 +9,7 @@ protocol ConnectionStorage {
@MainActor
protocol ConfigurationStorage {
func loadTrackingPreferences() throws -> TrackingPreferences
func load(allowInteraction: Bool) throws -> AppConfiguration
func save(_ configuration: AppConfiguration) throws
}
@@ -37,7 +38,19 @@ final class PreferencesConfigurationStorage: ConfigurationStorage {
self.connectionStorage = connectionStorage
}
func load(allowInteraction: Bool) throws -> AppConfiguration {
static func live() -> PreferencesConfigurationStorage {
PreferencesConfigurationStorage(preferences: .standard, connectionStorage: KeychainConnectionStorage(
service: "\(Bundle.main.bundleIdentifier ?? "com.codigojose.traccar-client-macos").configuration"
))
}
func loadTrackingPreferences() throws -> TrackingPreferences {
let tracking = try readRecord()?.tracking ?? TrackingPreferences()
try tracking.validate()
return tracking
}
private func readRecord() throws -> Record? {
let data = preferences.data(forKey: Self.preferencesKey)
let record: Record?
if let data {
@@ -52,6 +65,11 @@ final class PreferencesConfigurationStorage: ConfigurationStorage {
} else {
record = nil
}
return record
}
func load(allowInteraction: Bool) throws -> AppConfiguration {
let record = try readRecord()
let connection = try connectionStorage.read(allowInteraction: allowInteraction)
if record != nil && connection == nil { throw ConfigurationError.missingIdentity }
var configuration = AppConfiguration()