diff --git a/BanYaroGo/Views/GiftkoederView.swift b/BanYaroGo/Views/GiftkoederView.swift index 1a3b61a..dacb8d8 100644 --- a/BanYaroGo/Views/GiftkoederView.swift +++ b/BanYaroGo/Views/GiftkoederView.swift @@ -7,6 +7,7 @@ struct GiftkoederView: View { @State private var isLoading = false @State private var errorMessage: String? @State private var showReport = false + @State private var cameraPosition: MapCameraPosition = .automatic var body: some View { Group { @@ -37,6 +38,7 @@ struct GiftkoederView: View { } .task { location.request() } .onChange(of: location.coordinate?.latitude) { _, _ in + if let c = location.coordinate { centerOn(c) } Task { await load() } } .sheet(isPresented: $showReport) { @@ -48,10 +50,7 @@ struct GiftkoederView: View { private func content(at coord: CLLocationCoordinate2D) -> some View { VStack(spacing: 0) { - Map(initialPosition: .region(MKCoordinateRegion( - center: coord, - span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) - ))) { + Map(position: $cameraPosition) { UserAnnotation() ForEach(alerts) { alert in Annotation(alert.typ ?? "Giftköder", @@ -67,6 +66,20 @@ struct GiftkoederView: View { } .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 alerts.isEmpty && !isLoading { 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 { guard let coord = location.coordinate else { return } isLoading = true diff --git a/BanYaroGo/Views/VerloreneHundeView.swift b/BanYaroGo/Views/VerloreneHundeView.swift index bd18127..378cfb0 100644 --- a/BanYaroGo/Views/VerloreneHundeView.swift +++ b/BanYaroGo/Views/VerloreneHundeView.swift @@ -8,57 +8,138 @@ struct VerloreneHundeView: View { @State private var isLoading = false @State private var errorMessage: String? @State private var showReport = false + @State private var cameraPosition: MapCameraPosition = .automatic + @State private var tappedDog: LostDog? var body: some View { - content - .navigationTitle("Verlorene Hunde") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - if location.coordinate != nil { - ToolbarItem(placement: .topBarTrailing) { - Button { - showReport = true - } label: { - Label("Melden", systemImage: "exclamationmark.bubble") - } + 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") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + if location.coordinate != nil { + ToolbarItem(placement: .topBarTrailing) { + Button { + showReport = true + } label: { + Label("Melden", systemImage: "exclamationmark.bubble") } } } - .sheet(isPresented: $showReport) { - if let coord = location.coordinate { - ReportLostDogSheet(coord: coord) { await load() } - } + } + .sheet(isPresented: $showReport) { + if let coord = location.coordinate { + ReportLostDogSheet(coord: coord) { await load() } } - .task { - location.request() - await load() + } + .sheet(item: $tappedDog) { dog in + NavigationStack { + LostDogDetailView(dog: dog) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Schließen") { tappedDog = nil } + } + } } - .onChange(of: location.coordinate?.latitude) { _, _ in - Task { await load() } - } - .refreshable { await load() } + } + .task { + location.request() + await load() + } + .onChange(of: location.coordinate?.latitude) { _, _ in + if let c = location.coordinate { centerOn(c) } + Task { await load() } + } + .refreshable { await load() } } - @ViewBuilder - private var content: some View { - if isLoading && lostDogs.isEmpty { - ProgressView() - } else if let errorMessage, lostDogs.isEmpty { - ContentUnavailableView("Konnte nicht laden", systemImage: "wifi.slash", description: Text(errorMessage)) - } else if lostDogs.isEmpty { - ContentUnavailableView( - "Keine vermissten Hunde", - systemImage: "checkmark.circle", - description: Text("In 25 km Umkreis sind aktuell keine Vermisstmeldungen aktiv.") - ) - } else { - List(lostDogs) { dog in - NavigationLink { - LostDogDetailView(dog: dog) - } label: { - LostDogRow(dog: dog) + private func content(at coord: CLLocationCoordinate2D) -> 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 { + ProgressView().padding(.top, 30) + Spacer() + } else if let errorMessage, lostDogs.isEmpty { + ContentUnavailableView( + "Konnte nicht laden", + systemImage: "wifi.slash", + description: Text(errorMessage) + ) + .padding(.top, 30) + Spacer() + } else if lostDogs.isEmpty { + ContentUnavailableView( + "Keine vermissten Hunde", + systemImage: "checkmark.circle", + description: Text("In 25 km Umkreis sind aktuell keine Vermisstmeldungen aktiv.") + ) + .padding(.top, 30) + Spacer() + } else { + List(lostDogs) { dog in + NavigationLink { + LostDogDetailView(dog: dog) + } label: { + 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) + )) } }