Karten: zentriert auf Standort + Re-Center-Button
- GiftkoederView: Map.position als State (war initialPosition, einmalig), Re-Center-Button rechts oben (location.fill in Capsule auf material), bei Standort-Update wird Kamera mit easeInOut animiert auf Position - VerloreneHundeView: dieselbe Map-Struktur ergänzt, fehlte komplett. Pins als magnifyingglass.circle.fill in Akzentfarbe, tappable → öffnet LostDogDetailView als Sheet (mit eigenem 'Schließen'-Button)
This commit is contained in:
parent
fb00468c8c
commit
a6ea7b5b8f
2 changed files with 147 additions and 44 deletions
|
|
@ -7,6 +7,7 @@ struct GiftkoederView: View {
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
@State private var showReport = false
|
@State private var showReport = false
|
||||||
|
@State private var cameraPosition: MapCameraPosition = .automatic
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
|
@ -37,6 +38,7 @@ struct GiftkoederView: View {
|
||||||
}
|
}
|
||||||
.task { location.request() }
|
.task { location.request() }
|
||||||
.onChange(of: location.coordinate?.latitude) { _, _ in
|
.onChange(of: location.coordinate?.latitude) { _, _ in
|
||||||
|
if let c = location.coordinate { centerOn(c) }
|
||||||
Task { await load() }
|
Task { await load() }
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showReport) {
|
.sheet(isPresented: $showReport) {
|
||||||
|
|
@ -48,10 +50,7 @@ struct GiftkoederView: View {
|
||||||
|
|
||||||
private func content(at coord: CLLocationCoordinate2D) -> some View {
|
private func content(at coord: CLLocationCoordinate2D) -> some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Map(initialPosition: .region(MKCoordinateRegion(
|
Map(position: $cameraPosition) {
|
||||||
center: coord,
|
|
||||||
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
|
|
||||||
))) {
|
|
||||||
UserAnnotation()
|
UserAnnotation()
|
||||||
ForEach(alerts) { alert in
|
ForEach(alerts) { alert in
|
||||||
Annotation(alert.typ ?? "Giftköder",
|
Annotation(alert.typ ?? "Giftköder",
|
||||||
|
|
@ -67,6 +66,20 @@ struct GiftkoederView: View {
|
||||||
}
|
}
|
||||||
.frame(height: 260)
|
.frame(height: 260)
|
||||||
.ignoresSafeArea(edges: .top)
|
.ignoresSafeArea(edges: .top)
|
||||||
|
.overlay(alignment: .topTrailing) {
|
||||||
|
Button {
|
||||||
|
centerOn(coord)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "location.fill")
|
||||||
|
.font(.callout.bold())
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
|
.padding(10)
|
||||||
|
.background(.thinMaterial, in: Circle())
|
||||||
|
.shadow(radius: 2)
|
||||||
|
}
|
||||||
|
.padding(.top, 60)
|
||||||
|
.padding(.trailing, 12)
|
||||||
|
}
|
||||||
|
|
||||||
if alerts.isEmpty && !isLoading {
|
if alerts.isEmpty && !isLoading {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
|
|
@ -85,6 +98,15 @@ struct GiftkoederView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func centerOn(_ coord: CLLocationCoordinate2D) {
|
||||||
|
withAnimation(.easeInOut(duration: 0.4)) {
|
||||||
|
cameraPosition = .region(MKCoordinateRegion(
|
||||||
|
center: coord,
|
||||||
|
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func load() async {
|
private func load() async {
|
||||||
guard let coord = location.coordinate else { return }
|
guard let coord = location.coordinate else { return }
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,23 @@ struct VerloreneHundeView: View {
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
@State private var showReport = false
|
@State private var showReport = false
|
||||||
|
@State private var cameraPosition: MapCameraPosition = .automatic
|
||||||
|
@State private var tappedDog: LostDog?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
content
|
Group {
|
||||||
|
if let coord = location.coordinate {
|
||||||
|
content(at: coord)
|
||||||
|
} else if location.error != nil {
|
||||||
|
ContentUnavailableView(
|
||||||
|
"Kein Standort",
|
||||||
|
systemImage: "location.slash",
|
||||||
|
description: Text(location.error ?? "")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ProgressView("Hole Standort…")
|
||||||
|
}
|
||||||
|
}
|
||||||
.navigationTitle("Verlorene Hunde")
|
.navigationTitle("Verlorene Hunde")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
@ -29,28 +43,84 @@ struct VerloreneHundeView: View {
|
||||||
ReportLostDogSheet(coord: coord) { await load() }
|
ReportLostDogSheet(coord: coord) { await load() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(item: $tappedDog) { dog in
|
||||||
|
NavigationStack {
|
||||||
|
LostDogDetailView(dog: dog)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
Button("Schließen") { tappedDog = nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.task {
|
.task {
|
||||||
location.request()
|
location.request()
|
||||||
await load()
|
await load()
|
||||||
}
|
}
|
||||||
.onChange(of: location.coordinate?.latitude) { _, _ in
|
.onChange(of: location.coordinate?.latitude) { _, _ in
|
||||||
|
if let c = location.coordinate { centerOn(c) }
|
||||||
Task { await load() }
|
Task { await load() }
|
||||||
}
|
}
|
||||||
.refreshable { await load() }
|
.refreshable { await load() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
private func content(at coord: CLLocationCoordinate2D) -> some View {
|
||||||
private var content: some View {
|
VStack(spacing: 0) {
|
||||||
|
Map(position: $cameraPosition) {
|
||||||
|
UserAnnotation()
|
||||||
|
ForEach(lostDogs) { dog in
|
||||||
|
Annotation(dog.name,
|
||||||
|
coordinate: CLLocationCoordinate2D(latitude: dog.lat, longitude: dog.lon)
|
||||||
|
) {
|
||||||
|
Button {
|
||||||
|
tappedDog = dog
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "magnifyingglass.circle.fill")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundStyle(.white, Color.accentColor)
|
||||||
|
.background(.white, in: Circle())
|
||||||
|
.shadow(radius: 2)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 260)
|
||||||
|
.ignoresSafeArea(edges: .top)
|
||||||
|
.overlay(alignment: .topTrailing) {
|
||||||
|
Button {
|
||||||
|
centerOn(coord)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "location.fill")
|
||||||
|
.font(.callout.bold())
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
|
.padding(10)
|
||||||
|
.background(.thinMaterial, in: Circle())
|
||||||
|
.shadow(radius: 2)
|
||||||
|
}
|
||||||
|
.padding(.top, 60)
|
||||||
|
.padding(.trailing, 12)
|
||||||
|
}
|
||||||
|
|
||||||
if isLoading && lostDogs.isEmpty {
|
if isLoading && lostDogs.isEmpty {
|
||||||
ProgressView()
|
ProgressView().padding(.top, 30)
|
||||||
|
Spacer()
|
||||||
} else if let errorMessage, lostDogs.isEmpty {
|
} else if let errorMessage, lostDogs.isEmpty {
|
||||||
ContentUnavailableView("Konnte nicht laden", systemImage: "wifi.slash", description: Text(errorMessage))
|
ContentUnavailableView(
|
||||||
|
"Konnte nicht laden",
|
||||||
|
systemImage: "wifi.slash",
|
||||||
|
description: Text(errorMessage)
|
||||||
|
)
|
||||||
|
.padding(.top, 30)
|
||||||
|
Spacer()
|
||||||
} else if lostDogs.isEmpty {
|
} else if lostDogs.isEmpty {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
"Keine vermissten Hunde",
|
"Keine vermissten Hunde",
|
||||||
systemImage: "checkmark.circle",
|
systemImage: "checkmark.circle",
|
||||||
description: Text("In 25 km Umkreis sind aktuell keine Vermisstmeldungen aktiv.")
|
description: Text("In 25 km Umkreis sind aktuell keine Vermisstmeldungen aktiv.")
|
||||||
)
|
)
|
||||||
|
.padding(.top, 30)
|
||||||
|
Spacer()
|
||||||
} else {
|
} else {
|
||||||
List(lostDogs) { dog in
|
List(lostDogs) { dog in
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
|
@ -59,6 +129,17 @@ struct VerloreneHundeView: View {
|
||||||
LostDogRow(dog: dog)
|
LostDogRow(dog: dog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func centerOn(_ coord: CLLocationCoordinate2D) {
|
||||||
|
withAnimation(.easeInOut(duration: 0.4)) {
|
||||||
|
cameraPosition = .region(MKCoordinateRegion(
|
||||||
|
center: coord,
|
||||||
|
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue