mirror of
https://github.com/jayanezj/traccar-client-macos.git
synced 2026-11-05 03:00:46 +01:00
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:
18 files changed
+1030
-38
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LocationStatusView: View {
|
||||
let location: LocationModel
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Label("Location capture", systemImage: "location")
|
||||
.font(.title2)
|
||||
Text("Capture your Mac's location continuously or once while paused. Nothing is sent to a server.")
|
||||
Text("Choose Start or Capture once to allow macOS to request location permission if needed.")
|
||||
.foregroundStyle(.secondary)
|
||||
Form {
|
||||
LabeledContent("Continuous capture", value: location.continuousRequested ? "Active" : "Paused")
|
||||
LabeledContent("Condition", value: location.status)
|
||||
LabeledContent("Location Services", value: location.availability.services.rawValue)
|
||||
LabeledContent("Permission", value: location.availability.permission.rawValue)
|
||||
LabeledContent("Single capture", value: location.singleStatus.rawValue)
|
||||
if let fix = location.lastFix {
|
||||
LabeledContent("Fix timestamp", value: fix.timestamp.formatted(date: .numeric, time: .standard))
|
||||
LabeledContent("Reported accuracy", value: "\(fix.horizontalAccuracy.formatted()) m")
|
||||
LabeledContent("Source", value: location.lastFixOrigin?.rawValue ?? "Location Services")
|
||||
} else {
|
||||
Text("No location received.").foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
if let error = location.errorMessage ?? location.continuousError {
|
||||
Label(error, systemImage: "exclamationmark.triangle")
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
HStack {
|
||||
Button(location.continuousRequested ? "Pause" : "Start") {
|
||||
if location.continuousRequested { location.pause() } else {
|
||||
location.startContinuous()
|
||||
}
|
||||
}
|
||||
Button("Capture once") {
|
||||
location.captureOnce()
|
||||
}
|
||||
.disabled(location.singleStatus == .waiting)
|
||||
if location.singleStatus == .waiting {
|
||||
Button("Cancel capture") { location.cancelSingle() }
|
||||
}
|
||||
Spacer()
|
||||
Button("Refresh status") { location.refreshAvailability() }
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.frame(width: 620, height: 540)
|
||||
.onAppear { location.refreshAvailability() }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@Bindable var model: ConfigurationModel
|
||||
let location: LocationModel
|
||||
@State private var focus = WindowFocusController()
|
||||
|
||||
var body: some View {
|
||||
@@ -122,7 +123,7 @@ struct SettingsView: View {
|
||||
TextField("Heartbeat (seconds)", value: $model.draft.tracking.heartbeatSeconds, format: .number)
|
||||
Toggle("Buffer positions offline", isOn: $model.draft.tracking.bufferingEnabled)
|
||||
Toggle("Detect stationary periods", isOn: $model.draft.tracking.stationaryDetectionEnabled)
|
||||
Text("Preferences are saved for later tracking implementation. Tracking remains paused in this build.")
|
||||
Text("Accuracy applies on capture start. Selection, buffering and stationary detection are not active yet.")
|
||||
}
|
||||
.disabled(!model.hasLoaded)
|
||||
}
|
||||
@@ -132,13 +133,16 @@ struct SettingsView: View {
|
||||
Toggle("Launch at login", isOn: $model.draft.tracking.launchAtLogin).disabled(true)
|
||||
Toggle("Restore continuous tracking", isOn: $model.draft.tracking.restoreTracking).disabled(true)
|
||||
Text("Login startup and tracking restoration will be available in a later build. Both default to off.")
|
||||
Text("Closing Settings keeps the app in the menu bar. Use Quit to exit.")
|
||||
Text("Closing Settings keeps requested capture running in the menu bar. Use Pause to stop capture.")
|
||||
}
|
||||
}
|
||||
|
||||
private var diagnostics: some View {
|
||||
Form {
|
||||
LabeledContent("Status", value: "Paused")
|
||||
LabeledContent("Location capture", value: location.continuousRequested ? "Active" : "Paused")
|
||||
LabeledContent("Condition", value: location.status)
|
||||
LabeledContent("Location permission", value: location.availability.permission.rawValue)
|
||||
LabeledContent("Location Services", value: location.availability.services.rawValue)
|
||||
LabeledContent("Configuration", value: model.hasLoaded ? "Loaded locally" : "Unavailable")
|
||||
Text("Connection and identity: Keychain. Tracking preferences: UserDefaults.")
|
||||
Text("No location permission requests or network activity are started by loading or saving settings.")
|
||||
|
||||
@@ -3,15 +3,35 @@ import SwiftUI
|
||||
|
||||
struct TrackingMenu: View {
|
||||
@Environment(\.openSettings) private var openSettings
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
let location: LocationModel
|
||||
|
||||
var body: some View {
|
||||
Text("Paused")
|
||||
Text("Tracking is not available in this build.")
|
||||
Text(location.continuousRequested ? "Location capture active" : "Paused")
|
||||
Text(location.status)
|
||||
Text("Local capture only. No positions are sent.")
|
||||
if let fix = location.lastFix {
|
||||
Text("Last fix: \(fix.timestamp.formatted(date: .numeric, time: .standard))")
|
||||
Text("Reported accuracy: \(fix.horizontalAccuracy.formatted()) m")
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Start tracking") {}
|
||||
.disabled(true)
|
||||
Button(location.continuousRequested ? "Pause location capture" : "Start location capture") {
|
||||
if location.continuousRequested { location.pause() } else if location.availability.canCapture {
|
||||
location.startContinuous()
|
||||
} else {
|
||||
showCaptureWindow()
|
||||
}
|
||||
}
|
||||
Button("Capture location once") {
|
||||
if location.availability.canCapture {
|
||||
location.captureOnce()
|
||||
}
|
||||
showCaptureWindow()
|
||||
}
|
||||
.disabled(location.singleStatus == .waiting)
|
||||
Button("Location status…") { showCaptureWindow() }
|
||||
Button("Send location now") {}
|
||||
.disabled(true)
|
||||
Button("Send SOS") {}
|
||||
@@ -38,8 +58,14 @@ struct TrackingMenu: View {
|
||||
Divider()
|
||||
|
||||
Button("Quit Traccar Client macOS") {
|
||||
location.shutdown()
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
.keyboardShortcut("q")
|
||||
}
|
||||
|
||||
private func showCaptureWindow() {
|
||||
NSApplication.shared.activate()
|
||||
openWindow(id: "location-capture")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user