Tagebuch: Geolocation + Reverse-Geocoding beim Anlegen
- OneShotLocation beim Sheet-Öffnen, Toggle 'Standort verwenden' (Default an) - CLGeocoder reverseGeocodeLocation (de_DE-Locale) füllt locationName aus Placemark.name, Fallback thoroughfare + locality. User kann überschreiben. - gps_lat/gps_lon werden mit dem Eintrag gesendet, wenn Toggle an — die PWA rendert daraus die POI-Karte im Tagebuch.
This commit is contained in:
parent
12f8ba0be8
commit
3059a5eb2c
1 changed files with 79 additions and 3 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import SwiftUI
|
||||
import PhotosUI
|
||||
import CoreLocation
|
||||
|
||||
struct AddDiaryEntrySheet: View {
|
||||
let dogId: Int
|
||||
|
|
@ -17,6 +18,10 @@ struct AddDiaryEntrySheet: View {
|
|||
@State private var photoSelection: [PhotosPickerItem] = []
|
||||
@State private var photoData: [Data] = []
|
||||
|
||||
@State private var location = OneShotLocation()
|
||||
@State private var attachLocation = true
|
||||
@State private var isReverseGeocoding = false
|
||||
|
||||
@State private var saveState: SaveState = .idle
|
||||
@State private var errorMessage: String?
|
||||
|
||||
|
|
@ -42,12 +47,55 @@ struct AddDiaryEntrySheet: View {
|
|||
}
|
||||
Section {
|
||||
Toggle("Meilenstein", isOn: $isMilestone)
|
||||
TextField("Ort (optional)", text: $locationName)
|
||||
TextField("Tags (komma-getrennt)", text: $tagsInput)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $attachLocation) {
|
||||
Label("Standort verwenden", systemImage: "location.fill")
|
||||
}
|
||||
if attachLocation {
|
||||
if let coord = location.coordinate {
|
||||
HStack {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.foregroundStyle(Color.accentColor)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
if !locationName.isEmpty {
|
||||
Text(locationName).font(.subheadline)
|
||||
} else if isReverseGeocoding {
|
||||
Text("Suche Ortsbezeichnung…")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
Text("Ort wird mitgespeichert")
|
||||
.font(.subheadline)
|
||||
}
|
||||
Text(String(format: "%.5f, %.5f", coord.latitude, coord.longitude))
|
||||
.font(.caption2.monospacedDigit())
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
}
|
||||
} else if location.error != nil {
|
||||
Label("Standort nicht verfügbar — wird ohne gespeichert", systemImage: "location.slash")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
HStack {
|
||||
ProgressView()
|
||||
Text("Hole Standort…").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
TextField("Ort überschreiben (optional)", text: $locationName)
|
||||
}
|
||||
} header: {
|
||||
Text("Standort")
|
||||
} footer: {
|
||||
Text("Der Standort wird zusammen mit dem Eintrag gespeichert — banyaro.app rendert daraus deine POI-Karte.")
|
||||
.font(.caption2)
|
||||
}
|
||||
|
||||
Section {
|
||||
PhotosPicker(
|
||||
selection: $photoSelection,
|
||||
|
|
@ -93,10 +141,37 @@ struct AddDiaryEntrySheet: View {
|
|||
.onChange(of: photoSelection) { _, items in
|
||||
Task { await loadPhotos(from: items) }
|
||||
}
|
||||
.task { location.request() }
|
||||
.onChange(of: location.coordinate?.latitude) { _, _ in
|
||||
guard let coord = location.coordinate, locationName.isEmpty else { return }
|
||||
Task { await reverseGeocode(coord) }
|
||||
}
|
||||
.interactiveDismissDisabled(saveState != .idle)
|
||||
}
|
||||
}
|
||||
|
||||
private func reverseGeocode(_ coord: CLLocationCoordinate2D) async {
|
||||
isReverseGeocoding = true
|
||||
defer { isReverseGeocoding = false }
|
||||
let geocoder = CLGeocoder()
|
||||
let loc = CLLocation(latitude: coord.latitude, longitude: coord.longitude)
|
||||
do {
|
||||
let placemarks = try await geocoder.reverseGeocodeLocation(loc, preferredLocale: Locale(identifier: "de_DE"))
|
||||
guard let p = placemarks.first else { return }
|
||||
// Sample: "Café Kaiser, Berlin" — name first, then locality fallback
|
||||
if let name = p.name, !name.isEmpty {
|
||||
locationName = name
|
||||
return
|
||||
}
|
||||
var parts: [String] = []
|
||||
if let street = p.thoroughfare { parts.append(street) }
|
||||
if let city = p.locality { parts.append(city) }
|
||||
if !parts.isEmpty { locationName = parts.joined(separator: ", ") }
|
||||
} catch {
|
||||
print("Reverse geocode failed: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var saveToolbarItem: some View {
|
||||
switch saveState {
|
||||
|
|
@ -132,14 +207,15 @@ struct AddDiaryEntrySheet: View {
|
|||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
let coord = attachLocation ? location.coordinate : nil
|
||||
let body = DiaryCreateBody(
|
||||
datum: formatter.string(from: date),
|
||||
typ: isMilestone ? "meilenstein" : "eintrag",
|
||||
titel: titel.trimmingCharacters(in: .whitespaces).isEmpty ? nil : titel.trimmingCharacters(in: .whitespaces),
|
||||
text: text.trimmingCharacters(in: .whitespaces).isEmpty ? nil : text.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
tags: tags.isEmpty ? nil : tags,
|
||||
gpsLat: nil,
|
||||
gpsLon: nil,
|
||||
gpsLat: coord?.latitude,
|
||||
gpsLon: coord?.longitude,
|
||||
locationName: locationName.trimmingCharacters(in: .whitespaces).isEmpty ? nil : locationName,
|
||||
isMilestone: isMilestone
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue