Analyze the GymFinder-iOS project at /Users/neog/Apps/iOS/GymFinder-iOS for bugs, inconsistencies, and improvement opportunities.

Read ALL Swift files in the project and identify:

1. **BUGS** - Actual code errors, crashes, memory leaks, race conditions
2. **INCONSISTENCIES** - Different patterns used for same things, naming inconsistencies
3. **PERFORMANCE ISSUES** - Inefficient code, unnecessary re-renders, missing optimizations
4. **UX ISSUES** - Missing loading states, error handling gaps, accessibility issues
5. **ARCHITECTURE ISSUES** - Coupling problems, missing abstractions, code duplication
6. **MISSING FEATURES** - Features that are partially implemented or referenced but not working

For each issue found, provide:
- File and line number
- Description of the issue
- Severity (Critical/High/Medium/Low)
- Suggested fix

Read these files:
- All files in GymFinder-iOS/GymFinder-iOS/
- Models/, Services/, ViewModels/, Views/ subdirectories

Be thorough and specific. This is a code audit.


I'll analyze the GymFinder-iOS project thoroughly by examining all Swift files. Let me start by exploring the project structure.


Bash

find /Users/neog/Apps/iOS/GymFinder-iOS -type f -name "*.swift" | head -20


Let me get the complete list of Swift files and then read them all:


find /Users/neog/Apps/iOS/GymFinder-iOS -type f -name "*.swift"


Now let me read all the Swift files:


     1→import SwiftUI
     2→
     3→@main
     4→struct GymFinderApp: App {
     5→    @StateObject private var locationService = LocationService()
     6→
     7→    var body: some Scene {
     8→        WindowGroup {
     9→            MainTabView()
    10→                .environmentObject(locationService)
    11→        }
    12→    }
    13→}
    14→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI

@main
struct GymFinderApp: App {
    @StateObject private var locationService = LocationService()

    var body: some Scene {
        WindowGroup {
            MainTabView()
                .environmentObject(locationService)
        }
    }
}


     1→import SwiftUI
     2→
     3→struct MainTabView: View {
     4→    @State private var selectedTab = 0
     5→
     6→    var body: some View {
     7→        TabView(selection: $selectedTab) {
     8→            MapView()
     9→                .tabItem {
    10→                    Label("Mapa", systemImage: "map.fill")
    11→                }
    12→                .tag(0)
    13→
    14→            GymsListView()
    15→                .tabItem {
    16→                    Label("Academias", systemImage: "dumbbell.fill")
    17→                }
    18→                .tag(1)
    19→
    20→            FavoritesView()
    21→                .tabItem {
    22→                    Label("Favoritos", systemImage: "heart.fill")
    23→                }
    24→                .tag(2)
    25→
    26→            SettingsView()
    27→                .tabItem {
    28→                    Label("Ajustes", systemImage: "gearshape.fill")
    29→                }
    30→                .tag(3)
    31→        }
    32→        .tint(.orange)
    33→    }
    34→}
    35→
    36→#Preview {
    37→    MainTabView()
    38→        .environmentObject(LocationService())
    39→}
    40→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI

struct MainTabView: View {
    @State private var selectedTab = 0

    var body: some View {
        TabView(selection: $selectedTab) {
            MapView()
                .tabItem {
                    Label("Mapa", systemImage: "map.fill")
                }
                .tag(0)

            GymsListView()
                .tabItem {
                    Label("Academias", systemImage: "dumbbell.fill")
                }
                .tag(1)

            FavoritesView()
                .tabItem {
                    Label("Favoritos", systemImage: "heart.fill")
                }
                .tag(2)

            SettingsView()
                .tabItem {
                    Label("Ajustes", systemImage: "gearshape.fill")
                }
                .tag(3)
        }
        .tint(.orange)
    }
}

#Preview {
    MainTabView()
        .environmentObject(LocationService())
}


     1→import Foundation
     2→import CoreLocation
     3→import MapKit
     4→
     5→struct Gym: Identifiable, Equatable, Hashable {
     6→    let id: String
     7→    let name: String
     8→    let address: String
     9→    let latitude: Double
    10→    let longitude: Double
    11→    let rating: Double?
    12→    let priceRange: PriceRange?
    13→    let amenities: [Amenity]
    14→    let openingHours: String?
    15→    let phone: String?
    16→    let website: String?
    17→    let imageURL: String?
    18→    var distance: Double? // Distance from user in meters
    19→    var mapItem: MKMapItem? // For Apple Maps integration
    20→
    21→    var coordinate: CLLocationCoordinate2D {
    22→        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    23→    }
    24→
    25→    var formattedDistance: String {
    26→        guard let distance = distance else { return "—" }
    27→        if distance < 1000 {
    28→            return "\(Int(distance))m"
    29→        } else {
    30→            return String(format: "%.1f km", distance / 1000)
    31→        }
    32→    }
    33→
    34→    var formattedPhone: String? {
    35→        phone?.replacingOccurrences(of: " ", with: "")
    36→            .replacingOccurrences(of: "-", with: "")
    37→            .replacingOccurrences(of: "(", with: "")
    38→            .replacingOccurrences(of: ")", with: "")
    39→    }
    40→
    41→    // MARK: - Equatable & Hashable
    42→    static func == (lhs: Gym, rhs: Gym) -> Bool {
    43→        lhs.id == rhs.id
    44→    }
    45→
    46→    func hash(into hasher: inout Hasher) {
    47→        hasher.combine(id)
    48→    }
    49→
    50→    // MARK: - Price Range
    51→    enum PriceRange: String, Codable, CaseIterable {
    52→        case budget = "$"
    53→        case moderate = "$$"
    54→        case premium = "$$$"
    55→        case luxury = "$$$$"
    56→
    57→        var displayName: String {
    58→            switch self {
    59→            case .budget: return "Econômico"
    60→            case .moderate: return "Moderado"
    61→            case .premium: return "Premium"
    62→            case .luxury: return "Luxo"
    63→            }
    64→        }
    65→    }
    66→
    67→    // MARK: - Amenities
    68→    enum Amenity: String, Codable, CaseIterable, Hashable {
    69→        case weights = "weights"
    70→        case cardio = "cardio"
    71→        case pool = "pool"
    72→        case sauna = "sauna"
    73→        case parking = "parking"
    74→        case classes = "classes"
    75→        case personalTrainer = "personal"
    76→        case lockers = "lockers"
    77→        case shower = "shower"
    78→        case wifi = "wifi"
    79→        case airConditioning = "ac"
    80→        case towels = "towels"
    81→
    82→        var displayName: String {
    83→            switch self {
    84→            case .weights: return "Musculação"
    85→            case .cardio: return "Cardio"
    86→            case .pool: return "Piscina"
    87→            case .sauna: return "Sauna"
    88→            case .parking: return "Estacionamento"
    89→            case .classes: return "Aulas"
    90→            case .personalTrainer: return "Personal"
    91→            case .lockers: return "Armários"
    92→            case .shower: return "Chuveiros"
    93→            case .wifi: return "Wi-Fi"
    94→            case .airConditioning: return "Ar Cond."
    95→            case .towels: return "Toalhas"
    96→            }
    97→        }
    98→
    99→        var icon: String {
   100→            switch self {
   101→            case .weights: return "dumbbell.fill"
   102→            case .cardio: return "figure.run"
   103→            case .pool: return "figure.pool.swim"
   104→            case .sauna: return "humidity.fill"
   105→            case .parking: return "car.fill"
   106→            case .classes: return "person.3.fill"
   107→            case .personalTrainer: return "person.fill"
   108→            case .lockers: return "lock.fill"
   109→            case .shower: return "drop.fill"
   110→            case .wifi: return "wifi"
   111→            case .airConditioning: return "snowflake"
   112→            case .towels: return "rectangle.stack.fill"
   113→            }
   114→        }
   115→    }
   116→}
   117→
   118→// MARK: - Apple Maps Actions
   119→extension Gym {
   120→    /// Open directions in Apple Maps
   121→    func openInMaps(mode: String = MKLaunchOptionsDirectionsModeDriving) {
   122→        let placemark = MKPlacemark(coordinate: coordinate)
   123→        let mapItem = self.mapItem ?? MKMapItem(placemark: placemark)
   124→        mapItem.name = name
   125→
   126→        mapItem.openInMaps(launchOptions: [
   127→            MKLaunchOptionsDirectionsModeKey: mode
   128→        ])
   129→    }
   130→
   131→    /// Open driving directions
   132→    func openDrivingDirections() {
   133→        openInMaps(mode: MKLaunchOptionsDirectionsModeDriving)
   134→    }
   135→
   136→    /// Open walking directions
   137→    func openWalkingDirections() {
   138→        openInMaps(mode: MKLaunchOptionsDirectionsModeWalking)
   139→    }
   140→
   141→    /// Open transit directions
   142→    func openTransitDirections() {
   143→        openInMaps(mode: MKLaunchOptionsDirectionsModeTransit)
   144→    }
   145→
   146→    /// Call the gym
   147→    func call() {
   148→        guard let phone = formattedPhone,
   149→              let url = URL(string: "tel:\(phone)") else { return }
   150→        UIApplication.shared.open(url)
   151→    }
   152→
   153→    /// Open website
   154→    func openWebsite() {
   155→        guard let website = website,
   156→              let url = URL(string: website) else { return }
   157→        UIApplication.shared.open(url)
   158→    }
   159→
   160→    /// Share gym info
   161→    func share(from viewController: UIViewController? = nil) {
   162→        var items: [Any] = [
   163→            "\(name)\n\(address)"
   164→        ]
   165→
   166→        // Add Apple Maps link
   167→        let mapsURL = URL(string: "https://maps.apple.com/?ll=\(latitude),\(longitude)&q=\(name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? name)")
   168→        if let url = mapsURL {
   169→            items.append(url)
   170→        }
   171→
   172→        let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
   173→
   174→        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
   175→           let rootVC = windowScene.windows.first?.rootViewController {
   176→            // For iPad
   177→            activityVC.popoverPresentationController?.sourceView = rootVC.view
   178→            rootVC.present(activityVC, animated: true)
   179→        }
   180→    }
   181→}
   182→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CoreLocation
import MapKit

struct Gym: Identifiable, Equatable, Hashable {
    let id: String
    let name: String
    let address: String
    let latitude: Double
    let longitude: Double
    let rating: Double?
    let priceRange: PriceRange?
    let amenities: [Amenity]
    let openingHours: String?
    let phone: String?
    let website: String?
    let imageURL: String?
    var distance: Double? // Distance from user in meters
    var mapItem: MKMapItem? // For Apple Maps integration

    var coordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }

    var formattedDistance: String {
        guard let distance = distance else { return "—" }
        if distance < 1000 {
            return "\(Int(distance))m"
        } else {
            return String(format: "%.1f km", distance / 1000)
        }
    }

    var formattedPhone: String? {
        phone?.replacingOccurrences(of: " ", with: "")
            .replacingOccurrences(of: "-", with: "")
            .replacingOccurrences(of: "(", with: "")
            .replacingOccurrences(of: ")", with: "")
    }

    // MARK: - Equatable & Hashable
    static func == (lhs: Gym, rhs: Gym) -> Bool {
        lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    // MARK: - Price Range
    enum PriceRange: String, Codable, CaseIterable {
        case budget = "$"
        case moderate = "$$"
        case premium = "$$$"
        case luxury = "$$$$"

        var displayName: String {
            switch self {
            case .budget: return "Econômico"
            case .moderate: return "Moderado"
            case .premium: return "Premium"
            case .luxury: return "Luxo"
            }
        }
    }

    // MARK: - Amenities
    enum Amenity: String, Codable, CaseIterable, Hashable {
        case weights = "weights"
        case cardio = "cardio"
        case pool = "pool"
        case sauna = "sauna"
        case parking = "parking"
        case classes = "classes"
        case personalTrainer = "personal"
        case lockers = "lockers"
        case shower = "shower"
        case wifi = "wifi"
        case airConditioning = "ac"
        case towels = "towels"

        var displayName: String {
            switch self {
            case .weights: return "Musculação"
            case .cardio: return "Cardio"
            case .pool: return "Piscina"
            case .sauna: return "Sauna"
            case .parking: return "Estacionamento"
            case .classes: return "Aulas"
            case .personalTrainer: return "Personal"
            case .lockers: return "Armários"
            case .shower: return "Chuveiros"
            case .wifi: return "Wi-Fi"
            case .airConditioning: return "Ar Cond."
            case .towels: return "Toalhas"
            }
        }

        var icon: String {
            switch self {
            case .weights: return "dumbbell.fill"
            case .cardio: return "figure.run"
            case .pool: return "figure.pool.swim"
            case .sauna: return "humidity.fill"
            case .parking: return "car.fill"
            case .classes: return "person.3.fill"
            case .personalTrainer: return "person.fill"
            case .lockers: return "lock.fill"
            case .shower: return "drop.fill"
            case .wifi: return "wifi"
            case .airConditioning: return "snowflake"
            case .towels: return "rectangle.stack.fill"
            }
        }
    }
}

// MARK: - Apple Maps Actions
extension Gym {
    /// Open directions in Apple Maps
    func openInMaps(mode: String = MKLaunchOptionsDirectionsModeDriving) {
        let placemark = MKPlacemark(coordinate: coordinate)
        let mapItem = self.mapItem ?? MKMapItem(placemark: placemark)
        mapItem.name = name

        mapItem.openInMaps(launchOptions: [
            MKLaunchOptionsDirectionsModeKey: mode
        ])
    }

    /// Open driving directions
    func openDrivingDirections() {
        openInMaps(mode: MKLaunchOptionsDirectionsModeDriving)
    }

    /// Open walking directions
    func openWalkingDirections() {
        openInMaps(mode: MKLaunchOptionsDirectionsModeWalking)
    }

    /// Open transit directions
    func openTransitDirections() {
        openInMaps(mode: MKLaunchOptionsDirectionsModeTransit)
    }

    /// Call the gym
    func call() {
        guard let phone = formattedPhone,
              let url = URL(string: "tel:\(phone)") else { return }
        UIApplication.shared.open(url)
    }

    /// Open website
    func openWebsite() {
        guard let website = website,
              let url = URL(string: website) else { return }
        UIApplication.shared.open(url)
    }

    /// Share gym info
    func share(from viewController: UIViewController? = nil) {
        var items: [Any] = [
            "\(name)\n\(address)"
        ]

        // Add Apple Maps link
        let mapsURL = URL(string: "https://maps.apple.com/?ll=\(latitude),\(longitude)&q=\(name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? name)")
        if let url = mapsURL {
            items.append(url)
        }

        let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)

        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let rootVC = windowScene.windows.first?.rootViewController {
            // For iPad
            activityVC.popoverPresentationController?.sourceView = rootVC.view
            rootVC.present(activityVC, animated: true)
        }
    }
}


     1→import Foundation
     2→
     3→struct GymFilter: Equatable {
     4→    var searchText: String = ""
     5→    var amenities: Set<Gym.Amenity> = []
     6→    var maxDistance: Double = 5000 // meters
     7→
     8→    var isActive: Bool {
     9→        !searchText.isEmpty ||
    10→        !amenities.isEmpty ||
    11→        maxDistance < 5000
    12→    }
    13→
    14→    mutating func reset() {
    15→        searchText = ""
    16→        amenities = []
    17→        maxDistance = 5000
    18→    }
    19→
    20→    var formattedMaxDistance: String {
    21→        if maxDistance >= 1000 {
    22→            return String(format: "%.0f km", maxDistance / 1000)
    23→        }
    24→        return "\(Int(maxDistance)) m"
    25→    }
    26→}
    27→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation

struct GymFilter: Equatable {
    var searchText: String = ""
    var amenities: Set<Gym.Amenity> = []
    var maxDistance: Double = 5000 // meters

    var isActive: Bool {
        !searchText.isEmpty ||
        !amenities.isEmpty ||
        maxDistance < 5000
    }

    mutating func reset() {
        searchText = ""
        amenities = []
        maxDistance = 5000
    }

    var formattedMaxDistance: String {
        if maxDistance >= 1000 {
            return String(format: "%.0f km", maxDistance / 1000)
        }
        return "\(Int(maxDistance)) m"
    }
}


     1→import Foundation
     2→import CoreLocation
     3→import MapKit
     4→import Combine
     5→
     6→@MainActor
     7→class GymsViewModel: ObservableObject {
     8→    @Published var gyms: [Gym] = []
     9→    @Published var filter = GymFilter()
    10→    @Published var isLoading = false
    11→    @Published var errorMessage: String?
    12→    @Published var selectedGym: Gym?
    13→    @Published var hasSearched = false
    14→
    15→    private let searchService = GymSearchService()
    16→    private var locationService: LocationService?
    17→    private var cancellables = Set<AnyCancellable>()
    18→
    19→    var filteredGyms: [Gym] {
    20→        var result = gyms
    21→
    22→        // Search text
    23→        if !filter.searchText.isEmpty {
    24→            result = result.filter {
    25→                $0.name.localizedCaseInsensitiveContains(filter.searchText) ||
    26→                $0.address.localizedCaseInsensitiveContains(filter.searchText)
    27→            }
    28→        }
    29→
    30→        // Amenities
    31→        if !filter.amenities.isEmpty {
    32→            result = result.filter { gym in
    33→                filter.amenities.isSubset(of: Set(gym.amenities))
    34→            }
    35→        }
    36→
    37→        // Distance filter
    38→        if filter.maxDistance < 10000 {
    39→            result = result.filter { gym in
    40→                (gym.distance ?? 0) <= filter.maxDistance
    41→            }
    42→        }
    43→
    44→        return result
    45→    }
    46→
    47→    func setLocationService(_ service: LocationService) {
    48→        self.locationService = service
    49→        observeLocationChanges()
    50→    }
    51→
    52→    /// Observe location changes and auto-search
    53→    private func observeLocationChanges() {
    54→        guard let locationService = locationService else { return }
    55→
    56→        // Observe location updates
    57→        locationService.$locationUpdated
    58→            .dropFirst()  // Skip initial value
    59→            .sink { [weak self] _ in
    60→                guard let self = self else { return }
    61→                Task {
    62→                    await self.searchGyms()
    63→                }
    64→            }
    65→            .store(in: &cancellables)
    66→
    67→        // Also observe when authorization changes to authorized
    68→        locationService.$authorizationStatus
    69→            .dropFirst()
    70→            .filter { $0 == .authorizedWhenInUse || $0 == .authorizedAlways }
    71→            .sink { [weak self] _ in
    72→                guard let self = self else { return }
    73→                // Small delay to let location update
    74→                Task {
    75→                    try? await Task.sleep(nanoseconds: 500_000_000)
    76→                    if !self.hasSearched {
    77→                        await self.searchGyms()
    78→                    }
    79→                }
    80→            }
    81→            .store(in: &cancellables)
    82→    }
    83→
    84→    /// Search for gyms using Apple's MKLocalSearch
    85→    func searchGyms() async {
    86→        guard let location = locationService?.currentLocation else {
    87→            // Don't show error if location not ready yet, it will auto-search when ready
    88→            if hasSearched {
    89→                errorMessage = "Localização não disponível"
    90→            }
    91→            return
    92→        }
    93→
    94→        isLoading = true
    95→        errorMessage = nil
    96→
    97→        await searchService.searchGyms(
    98→            near: location.coordinate,
    99→            radius: filter.maxDistance
   100→        )
   101→
   102→        gyms = searchService.searchResults
   103→        errorMessage = searchService.errorMessage
   104→        hasSearched = true
   105→        isLoading = false
   106→    }
   107→
   108→    /// Search with custom query
   109→    func searchGyms(query: String) async {
   110→        guard let location = locationService?.currentLocation else {
   111→            errorMessage = "Localização não disponível"
   112→            return
   113→        }
   114→
   115→        isLoading = true
   116→        errorMessage = nil
   117→
   118→        await searchService.searchGyms(
   119→            query: query,
   120→            near: location.coordinate,
   121→            radius: filter.maxDistance
   122→        )
   123→
   124→        gyms = searchService.searchResults
   125→        errorMessage = searchService.errorMessage
   126→        isLoading = false
   127→    }
   128→
   129→    /// Refresh gym list
   130→    func refresh() async {
   131→        await searchGyms()
   132→    }
   133→
   134→    /// Load gyms when location becomes available
   135→    func loadGyms() async {
   136→        // Wait briefly for location
   137→        if locationService?.currentLocation == nil {
   138→            try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
   139→        }
   140→
   141→        await searchGyms()
   142→    }
   143→}
   144→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CoreLocation
import MapKit
import Combine

@MainActor
class GymsViewModel: ObservableObject {
    @Published var gyms: [Gym] = []
    @Published var filter = GymFilter()
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var selectedGym: Gym?
    @Published var hasSearched = false

    private let searchService = GymSearchService()
    private var locationService: LocationService?
    private var cancellables = Set<AnyCancellable>()

    var filteredGyms: [Gym] {
        var result = gyms

        // Search text
        if !filter.searchText.isEmpty {
            result = result.filter {
                $0.name.localizedCaseInsensitiveContains(filter.searchText) ||
                $0.address.localizedCaseInsensitiveContains(filter.searchText)
            }
        }

        // Amenities
        if !filter.amenities.isEmpty {
            result = result.filter { gym in
                filter.amenities.isSubset(of: Set(gym.amenities))
            }
        }

        // Distance filter
        if filter.maxDistance < 10000 {
            result = result.filter { gym in
                (gym.distance ?? 0) <= filter.maxDistance
            }
        }

        return result
    }

    func setLocationService(_ service: LocationService) {
        self.locationService = service
        observeLocationChanges()
    }

    /// Observe location changes and auto-search
    private func observeLocationChanges() {
        guard let locationService = locationService else { return }

        // Observe location updates
        locationService.$locationUpdated
            .dropFirst()  // Skip initial value
            .sink { [weak self] _ in
                guard let self = self else { return }
                Task {
                    await self.searchGyms()
                }
            }
            .store(in: &cancellables)

        // Also observe when authorization changes to authorized
        locationService.$authorizationStatus
            .dropFirst()
            .filter { $0 == .authorizedWhenInUse || $0 == .authorizedAlways }
            .sink { [weak self] _ in
                guard let self = self else { return }
                // Small delay to let location update
                Task {
                    try? await Task.sleep(nanoseconds: 500_000_000)
                    if !self.hasSearched {
                        await self.searchGyms()
                    }
                }
            }
            .store(in: &cancellables)
    }

    /// Search for gyms using Apple's MKLocalSearch
    func searchGyms() async {
        guard let location = locationService?.currentLocation else {
            // Don't show error if location not ready yet, it will auto-search when ready
            if hasSearched {
                errorMessage = "Localização não disponível"
            }
            return
        }

        isLoading = true
        errorMessage = nil

        await searchService.searchGyms(
            near: location.coordinate,
            radius: filter.maxDistance
        )

        gyms = searchService.searchResults
        errorMessage = searchService.errorMessage
        hasSearched = true
        isLoading = false
    }

    /// Search with custom query
    func searchGyms(query: String) async {
        guard let location = locationService?.currentLocation else {
            errorMessage = "Localização não disponível"
            return
        }

        isLoading = true
        errorMessage = nil

        await searchService.searchGyms(
            query: query,
            near: location.coordinate,
            radius: filter.maxDistance
        )

        gyms = searchService.searchResults
        errorMessage = searchService.errorMessage
        isLoading = false
    }

    /// Refresh gym list
    func refresh() async {
        await searchGyms()
    }

    /// Load gyms when location becomes available
    func loadGyms() async {
        // Wait briefly for location
        if locationService?.currentLocation == nil {
            try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
        }

        await searchGyms()
    }
}


Now let me read the remaining service and view files:


     1→import Foundation
     2→import CoreLocation
     3→import Combine
     4→
     5→@MainActor
     6→class LocationService: NSObject, ObservableObject {
     7→    @Published var currentLocation: CLLocation?
     8→    @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
     9→    @Published var errorMessage: String?
    10→    @Published var locationUpdated: Date = Date()  // Trigger for observers
    11→
    12→    private let locationManager = CLLocationManager()
    13→    private var lastSignificantLocation: CLLocation?
    14→    private let significantDistance: Double = 100  // 100m threshold for refresh
    15→
    16→    override init() {
    17→        super.init()
    18→        locationManager.delegate = self
    19→        locationManager.desiredAccuracy = kCLLocationAccuracyBest
    20→        locationManager.distanceFilter = 50
    21→    }
    22→
    23→    func requestPermission() {
    24→        locationManager.requestWhenInUseAuthorization()
    25→    }
    26→
    27→    func startUpdating() {
    28→        locationManager.startUpdatingLocation()
    29→    }
    30→
    31→    func stopUpdating() {
    32→        locationManager.stopUpdatingLocation()
    33→    }
    34→
    35→    /// Check if location changed significantly (for triggering new searches)
    36→    func hasMovedSignificantly(from oldLocation: CLLocation?) -> Bool {
    37→        guard let current = currentLocation, let old = oldLocation else {
    38→            return currentLocation != nil
    39→        }
    40→        return current.distance(from: old) > significantDistance
    41→    }
    42→
    43→    func distance(to coordinate: CLLocationCoordinate2D) -> Double? {
    44→        guard let current = currentLocation else { return nil }
    45→        let destination = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    46→        return current.distance(from: destination)
    47→    }
    48→
    49→    func formattedDistance(to coordinate: CLLocationCoordinate2D) -> String {
    50→        guard let distance = distance(to: coordinate) else { return "—" }
    51→        if distance < 1000 {
    52→            return "\(Int(distance))m"
    53→        } else {
    54→            return String(format: "%.1fkm", distance / 1000)
    55→        }
    56→    }
    57→}
    58→
    59→extension LocationService: CLLocationManagerDelegate {
    60→    nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    61→        guard let location = locations.last else { return }
    62→        Task { @MainActor in
    63→            let oldLocation = self.currentLocation
    64→            self.currentLocation = location
    65→
    66→            // Notify if this is first location or significant movement
    67→            if oldLocation == nil || self.hasMovedSignificantly(from: oldLocation) {
    68→                self.lastSignificantLocation = location
    69→                self.locationUpdated = Date()  // Trigger observers
    70→            }
    71→        }
    72→    }
    73→
    74→    nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    75→        Task { @MainActor in
    76→            self.errorMessage = error.localizedDescription
    77→        }
    78→    }
    79→
    80→    nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    81→        Task { @MainActor in
    82→            self.authorizationStatus = manager.authorizationStatus
    83→            if manager.authorizationStatus == .authorizedWhenInUse ||
    84→               manager.authorizationStatus == .authorizedAlways {
    85→                self.startUpdating()
    86→            }
    87→        }
    88→    }
    89→}
    90→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CoreLocation
import Combine

@MainActor
class LocationService: NSObject, ObservableObject {
    @Published var currentLocation: CLLocation?
    @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
    @Published var errorMessage: String?
    @Published var locationUpdated: Date = Date()  // Trigger for observers

    private let locationManager = CLLocationManager()
    private var lastSignificantLocation: CLLocation?
    private let significantDistance: Double = 100  // 100m threshold for refresh

    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = 50
    }

    func requestPermission() {
        locationManager.requestWhenInUseAuthorization()
    }

    func startUpdating() {
        locationManager.startUpdatingLocation()
    }

    func stopUpdating() {
        locationManager.stopUpdatingLocation()
    }

    /// Check if location changed significantly (for triggering new searches)
    func hasMovedSignificantly(from oldLocation: CLLocation?) -> Bool {
        guard let current = currentLocation, let old = oldLocation else {
            return currentLocation != nil
        }
        return current.distance(from: old) > significantDistance
    }

    func distance(to coordinate: CLLocationCoordinate2D) -> Double? {
        guard let current = currentLocation else { return nil }
        let destination = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
        return current.distance(from: destination)
    }

    func formattedDistance(to coordinate: CLLocationCoordinate2D) -> String {
        guard let distance = distance(to: coordinate) else { return "—" }
        if distance < 1000 {
            return "\(Int(distance))m"
        } else {
            return String(format: "%.1fkm", distance / 1000)
        }
    }
}

extension LocationService: CLLocationManagerDelegate {
    nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        Task { @MainActor in
            let oldLocation = self.currentLocation
            self.currentLocation = location

            // Notify if this is first location or significant movement
            if oldLocation == nil || self.hasMovedSignificantly(from: oldLocation) {
                self.lastSignificantLocation = location
                self.locationUpdated = Date()  // Trigger observers
            }
        }
    }

    nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        Task { @MainActor in
            self.errorMessage = error.localizedDescription
        }
    }

    nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        Task { @MainActor in
            self.authorizationStatus = manager.authorizationStatus
            if manager.authorizationStatus == .authorizedWhenInUse ||
               manager.authorizationStatus == .authorizedAlways {
                self.startUpdating()
            }
        }
    }
}


     1→import Foundation
     2→import MapKit
     3→
     4→/// Service that uses Apple's MKLocalSearch to find real gyms and fitness centers
     5→@MainActor
     6→class GymSearchService: ObservableObject {
     7→    @Published var searchResults: [Gym] = []
     8→    @Published var isSearching = false
     9→    @Published var errorMessage: String?
    10→
    11→    private var currentSearch: MKLocalSearch?
    12→
    13→    /// Search for gyms near a location using Apple's MapKit POI database
    14→    func searchGyms(near coordinate: CLLocationCoordinate2D, radius: CLLocationDistance = 5000) async {
    15→        // Cancel any existing search
    16→        currentSearch?.cancel()
    17→
    18→        isSearching = true
    19→        errorMessage = nil
    20→
    21→        let request = MKLocalSearch.Request()
    22→        request.naturalLanguageQuery = "academia fitness gym"
    23→        request.resultTypes = .pointOfInterest
    24→
    25→        // Define search region
    26→        let region = MKCoordinateRegion(
    27→            center: coordinate,
    28→            latitudinalMeters: radius * 2,
    29→            longitudinalMeters: radius * 2
    30→        )
    31→        request.region = region
    32→
    33→        let search = MKLocalSearch(request: request)
    34→        currentSearch = search
    35→
    36→        do {
    37→            let response = try await search.start()
    38→            searchResults = response.mapItems.compactMap { mapItem in
    39→                convertToGym(mapItem: mapItem, userLocation: coordinate)
    40→            }
    41→            .sorted { ($0.distance ?? .infinity) < ($1.distance ?? .infinity) }
    42→
    43→            isSearching = false
    44→        } catch {
    45→            if (error as NSError).code != MKError.Code.placemarkNotFound.rawValue {
    46→                errorMessage = error.localizedDescription
    47→            }
    48→            isSearching = false
    49→        }
    50→    }
    51→
    52→    /// Search with a text query
    53→    func searchGyms(query: String, near coordinate: CLLocationCoordinate2D, radius: CLLocationDistance = 10000) async {
    54→        currentSearch?.cancel()
    55→
    56→        isSearching = true
    57→        errorMessage = nil
    58→
    59→        let request = MKLocalSearch.Request()
    60→        request.naturalLanguageQuery = query.isEmpty ? "academia fitness gym" : query
    61→        request.resultTypes = .pointOfInterest
    62→
    63→        let region = MKCoordinateRegion(
    64→            center: coordinate,
    65→            latitudinalMeters: radius * 2,
    66→            longitudinalMeters: radius * 2
    67→        )
    68→        request.region = region
    69→
    70→        let search = MKLocalSearch(request: request)
    71→        currentSearch = search
    72→
    73→        do {
    74→            let response = try await search.start()
    75→            searchResults = response.mapItems.compactMap { mapItem in
    76→                convertToGym(mapItem: mapItem, userLocation: coordinate)
    77→            }
    78→            .sorted { ($0.distance ?? .infinity) < ($1.distance ?? .infinity) }
    79→
    80→            isSearching = false
    81→        } catch {
    82→            errorMessage = error.localizedDescription
    83→            isSearching = false
    84→        }
    85→    }
    86→
    87→    /// Convert MKMapItem to our Gym model
    88→    private func convertToGym(mapItem: MKMapItem, userLocation: CLLocationCoordinate2D) -> Gym? {
    89→        guard let name = mapItem.name,
    90→              let location = mapItem.placemark.location else {
    91→            return nil
    92→        }
    93→
    94→        // Calculate distance
    95→        let userCLLocation = CLLocation(latitude: userLocation.latitude, longitude: userLocation.longitude)
    96→        let distance = location.distance(from: userCLLocation)
    97→
    98→        // Build address
    99→        let placemark = mapItem.placemark
   100→        var addressComponents: [String] = []
   101→        if let thoroughfare = placemark.thoroughfare {
   102→            if let subThoroughfare = placemark.subThoroughfare {
   103→                addressComponents.append("\(thoroughfare), \(subThoroughfare)")
   104→            } else {
   105→                addressComponents.append(thoroughfare)
   106→            }
   107→        }
   108→        if let subLocality = placemark.subLocality {
   109→            addressComponents.append(subLocality)
   110→        }
   111→        if let locality = placemark.locality {
   112→            addressComponents.append(locality)
   113→        }
   114→
   115→        let address = addressComponents.isEmpty ? "Endereço não disponível" : addressComponents.joined(separator: " - ")
   116→
   117→        return Gym(
   118→            id: mapItem.placemark.coordinate.latitude.description + mapItem.placemark.coordinate.longitude.description,
   119→            name: name,
   120→            address: address,
   121→            latitude: location.coordinate.latitude,
   122→            longitude: location.coordinate.longitude,
   123→            rating: nil, // Apple doesn't provide ratings
   124→            priceRange: nil,
   125→            amenities: detectAmenities(from: mapItem),
   126→            openingHours: nil, // Would need to fetch from place details
   127→            phone: mapItem.phoneNumber,
   128→            website: mapItem.url?.absoluteString,
   129→            imageURL: nil,
   130→            distance: distance,
   131→            mapItem: mapItem // Store for directions
   132→        )
   133→    }
   134→
   135→    /// Try to detect amenities from the place category
   136→    private func detectAmenities(from mapItem: MKMapItem) -> [Gym.Amenity] {
   137→        var amenities: [Gym.Amenity] = [.weights, .cardio] // Default for gyms
   138→
   139→        let name = mapItem.name?.lowercased() ?? ""
   140→
   141→        if name.contains("crossfit") || name.contains("funcional") {
   142→            amenities.append(.classes)
   143→        }
   144→        if name.contains("natação") || name.contains("pool") || name.contains("aqua") {
   145→            amenities.append(.pool)
   146→        }
   147→        if name.contains("personal") || name.contains("studio") {
   148→            amenities.append(.personalTrainer)
   149→        }
   150→        if name.contains("smart") || name.contains("bluefit") || name.contains("selfit") {
   151→            // Budget gyms
   152→            amenities.append(.lockers)
   153→            amenities.append(.shower)
   154→        }
   155→        if name.contains("bodytech") || name.contains("competition") || name.contains("runner") {
   156→            // Premium gyms
   157→            amenities.append(.classes)
   158→            amenities.append(.personalTrainer)
   159→            amenities.append(.sauna)
   160→            amenities.append(.parking)
   161→        }
   162→
   163→        return amenities
   164→    }
   165→
   166→    func cancelSearch() {
   167→        currentSearch?.cancel()
   168→        isSearching = false
   169→    }
   170→}
   171→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import MapKit

/// Service that uses Apple's MKLocalSearch to find real gyms and fitness centers
@MainActor
class GymSearchService: ObservableObject {
    @Published var searchResults: [Gym] = []
    @Published var isSearching = false
    @Published var errorMessage: String?

    private var currentSearch: MKLocalSearch?

    /// Search for gyms near a location using Apple's MapKit POI database
    func searchGyms(near coordinate: CLLocationCoordinate2D, radius: CLLocationDistance = 5000) async {
        // Cancel any existing search
        currentSearch?.cancel()

        isSearching = true
        errorMessage = nil

        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = "academia fitness gym"
        request.resultTypes = .pointOfInterest

        // Define search region
        let region = MKCoordinateRegion(
            center: coordinate,
            latitudinalMeters: radius * 2,
            longitudinalMeters: radius * 2
        )
        request.region = region

        let search = MKLocalSearch(request: request)
        currentSearch = search

        do {
            let response = try await search.start()
            searchResults = response.mapItems.compactMap { mapItem in
                convertToGym(mapItem: mapItem, userLocation: coordinate)
            }
            .sorted { ($0.distance ?? .infinity) < ($1.distance ?? .infinity) }

            isSearching = false
        } catch {
            if (error as NSError).code != MKError.Code.placemarkNotFound.rawValue {
                errorMessage = error.localizedDescription
            }
            isSearching = false
        }
    }

    /// Search with a text query
    func searchGyms(query: String, near coordinate: CLLocationCoordinate2D, radius: CLLocationDistance = 10000) async {
        currentSearch?.cancel()

        isSearching = true
        errorMessage = nil

        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query.isEmpty ? "academia fitness gym" : query
        request.resultTypes = .pointOfInterest

        let region = MKCoordinateRegion(
            center: coordinate,
            latitudinalMeters: radius * 2,
            longitudinalMeters: radius * 2
        )
        request.region = region

        let search = MKLocalSearch(request: request)
        currentSearch = search

        do {
            let response = try await search.start()
            searchResults = response.mapItems.compactMap { mapItem in
                convertToGym(mapItem: mapItem, userLocation: coordinate)
            }
            .sorted { ($0.distance ?? .infinity) < ($1.distance ?? .infinity) }

            isSearching = false
        } catch {
            errorMessage = error.localizedDescription
            isSearching = false
        }
    }

    /// Convert MKMapItem to our Gym model
    private func convertToGym(mapItem: MKMapItem, userLocation: CLLocationCoordinate2D) -> Gym? {
        guard let name = mapItem.name,
              let location = mapItem.placemark.location else {
            return nil
        }

        // Calculate distance
        let userCLLocation = CLLocation(latitude: userLocation.latitude, longitude: userLocation.longitude)
        let distance = location.distance(from: userCLLocation)

        // Build address
        let placemark = mapItem.placemark
        var addressComponents: [String] = []
        if let thoroughfare = placemark.thoroughfare {
            if let subThoroughfare = placemark.subThoroughfare {
                addressComponents.append("\(thoroughfare), \(subThoroughfare)")
            } else {
                addressComponents.append(thoroughfare)
            }
        }
        if let subLocality = placemark.subLocality {
            addressComponents.append(subLocality)
        }
        if let locality = placemark.locality {
            addressComponents.append(locality)
        }

        let address = addressComponents.isEmpty ? "Endereço não disponível" : addressComponents.joined(separator: " - ")

        return Gym(
            id: mapItem.placemark.coordinate.latitude.description + mapItem.placemark.coordinate.longitude.description,
            name: name,
            address: address,
            latitude: location.coordinate.latitude,
            longitude: location.coordinate.longitude,
            rating: nil, // Apple doesn't provide ratings
            priceRange: nil,
            amenities: detectAmenities(from: mapItem),
            openingHours: nil, // Would need to fetch from place details
            phone: mapItem.phoneNumber,
            website: mapItem.url?.absoluteString,
            imageURL: nil,
            distance: distance,
            mapItem: mapItem // Store for directions
        )
    }

    /// Try to detect amenities from the place category
    private func detectAmenities(from mapItem: MKMapItem) -> [Gym.Amenity] {
        var amenities: [Gym.Amenity] = [.weights, .cardio] // Default for gyms

        let name = mapItem.name?.lowercased() ?? ""

        if name.contains("crossfit") || name.contains("funcional") {
            amenities.append(.classes)
        }
        if name.contains("natação") || name.contains("pool") || name.contains("aqua") {
            amenities.append(.pool)
        }
        if name.contains("personal") || name.contains("studio") {
            amenities.append(.personalTrainer)
        }
        if name.contains("smart") || name.contains("bluefit") || name.contains("selfit") {
            // Budget gyms
            amenities.append(.lockers)
            amenities.append(.shower)
        }
        if name.contains("bodytech") || name.contains("competition") || name.contains("runner") {
            // Premium gyms
            amenities.append(.classes)
            amenities.append(.personalTrainer)
            amenities.append(.sauna)
            amenities.append(.parking)
        }

        return amenities
    }

    func cancelSearch() {
        currentSearch?.cancel()
        isSearching = false
    }
}


     1→import Foundation
     2→import Combine
     3→
     4→/// Service for managing favorite gyms with local storage and CloudKit sync
     5→@MainActor
     6→class FavoritesService: ObservableObject {
     7→    @Published private(set) var favoriteIDs: Set<String> = []
     8→    @Published var isSyncing = false
     9→    @Published var lastSyncDate: Date?
    10→
    11→    private let userDefaultsKey = "gym_favorites"
    12→    private let cloudKit = CloudKitService.shared
    13→    private var cancellables = Set<AnyCancellable>()
    14→
    15→    init() {
    16→        loadFromLocal()
    17→        setupCloudKitSync()
    18→    }
    19→
    20→    // MARK: - Public API
    21→
    22→    func isFavorite(_ gymId: String) -> Bool {
    23→        favoriteIDs.contains(gymId)
    24→    }
    25→
    26→    func toggleFavorite(_ gymId: String) {
    27→        if favoriteIDs.contains(gymId) {
    28→            removeFavorite(gymId)
    29→        } else {
    30→            addFavorite(gymId)
    31→        }
    32→    }
    33→
    34→    func addFavorite(_ gymId: String) {
    35→        favoriteIDs.insert(gymId)
    36→        saveToLocal()
    37→
    38→        // Sync to CloudKit
    39→        Task {
    40→            await cloudKit.addFavorite(gymId)
    41→        }
    42→    }
    43→
    44→    func removeFavorite(_ gymId: String) {
    45→        favoriteIDs.remove(gymId)
    46→        saveToLocal()
    47→
    48→        // Sync to CloudKit
    49→        Task {
    50→            await cloudKit.removeFavorite(gymId)
    51→        }
    52→    }
    53→
    54→    // MARK: - Sync
    55→
    56→    func syncWithCloud() async {
    57→        guard cloudKit.isAvailable else { return }
    58→
    59→        isSyncing = true
    60→
    61→        // Fetch from CloudKit
    62→        let cloudFavorites = await cloudKit.fetchFavorites()
    63→
    64→        // Merge: union of local and cloud
    65→        let merged = favoriteIDs.union(cloudFavorites)
    66→
    67→        // Update local if different
    68→        if merged != favoriteIDs {
    69→            favoriteIDs = merged
    70→            saveToLocal()
    71→        }
    72→
    73→        // Push merged set to cloud
    74→        await cloudKit.saveFavorites(merged)
    75→
    76→        lastSyncDate = Date()
    77→        isSyncing = false
    78→    }
    79→
    80→    func forceSync() async {
    81→        await syncWithCloud()
    82→    }
    83→
    84→    // MARK: - Local Storage
    85→
    86→    private func loadFromLocal() {
    87→        if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
    88→           let ids = try? JSONDecoder().decode(Set<String>.self, from: data) {
    89→            favoriteIDs = ids
    90→        }
    91→    }
    92→
    93→    private func saveToLocal() {
    94→        if let data = try? JSONEncoder().encode(favoriteIDs) {
    95→            UserDefaults.standard.set(data, forKey: userDefaultsKey)
    96→        }
    97→    }
    98→
    99→    // MARK: - CloudKit Setup
   100→
   101→    private func setupCloudKitSync() {
   102→        // Check iCloud status and sync on launch
   103→        Task {
   104→            await cloudKit.checkAccountStatus()
   105→
   106→            if cloudKit.isAvailable {
   107→                await syncWithCloud()
   108→                await cloudKit.subscribeToChanges()
   109→            }
   110→        }
   111→
   112→        // Observe CloudKit status changes
   113→        cloudKit.$iCloudStatus
   114→            .dropFirst()
   115→            .sink { [weak self] status in
   116→                if status == .available {
   117→                    Task {
   118→                        await self?.syncWithCloud()
   119→                    }
   120→                }
   121→            }
   122→            .store(in: &cancellables)
   123→    }
   124→
   125→    // MARK: - Export/Import
   126→
   127→    func exportFavorites() -> Data? {
   128→        try? JSONEncoder().encode(Array(favoriteIDs))
   129→    }
   130→
   131→    func importFavorites(from data: Data) {
   132→        guard let ids = try? JSONDecoder().decode([String].self, from: data) else { return }
   133→        favoriteIDs = Set(ids)
   134→        saveToLocal()
   135→
   136→        Task {
   137→            await cloudKit.saveFavorites(favoriteIDs)
   138→        }
   139→    }
   140→
   141→    func clearAll() {
   142→        favoriteIDs.removeAll()
   143→        saveToLocal()
   144→
   145→        Task {
   146→            await cloudKit.saveFavorites([])
   147→        }
   148→    }
   149→}
   150→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import Combine

/// Service for managing favorite gyms with local storage and CloudKit sync
@MainActor
class FavoritesService: ObservableObject {
    @Published private(set) var favoriteIDs: Set<String> = []
    @Published var isSyncing = false
    @Published var lastSyncDate: Date?

    private let userDefaultsKey = "gym_favorites"
    private let cloudKit = CloudKitService.shared
    private var cancellables = Set<AnyCancellable>()

    init() {
        loadFromLocal()
        setupCloudKitSync()
    }

    // MARK: - Public API

    func isFavorite(_ gymId: String) -> Bool {
        favoriteIDs.contains(gymId)
    }

    func toggleFavorite(_ gymId: String) {
        if favoriteIDs.contains(gymId) {
            removeFavorite(gymId)
        } else {
            addFavorite(gymId)
        }
    }

    func addFavorite(_ gymId: String) {
        favoriteIDs.insert(gymId)
        saveToLocal()

        // Sync to CloudKit
        Task {
            await cloudKit.addFavorite(gymId)
        }
    }

    func removeFavorite(_ gymId: String) {
        favoriteIDs.remove(gymId)
        saveToLocal()

        // Sync to CloudKit
        Task {
            await cloudKit.removeFavorite(gymId)
        }
    }

    // MARK: - Sync

    func syncWithCloud() async {
        guard cloudKit.isAvailable else { return }

        isSyncing = true

        // Fetch from CloudKit
        let cloudFavorites = await cloudKit.fetchFavorites()

        // Merge: union of local and cloud
        let merged = favoriteIDs.union(cloudFavorites)

        // Update local if different
        if merged != favoriteIDs {
            favoriteIDs = merged
            saveToLocal()
        }

        // Push merged set to cloud
        await cloudKit.saveFavorites(merged)

        lastSyncDate = Date()
        isSyncing = false
    }

    func forceSync() async {
        await syncWithCloud()
    }

    // MARK: - Local Storage

    private func loadFromLocal() {
        if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
           let ids = try? JSONDecoder().decode(Set<String>.self, from: data) {
            favoriteIDs = ids
        }
    }

    private func saveToLocal() {
        if let data = try? JSONEncoder().encode(favoriteIDs) {
            UserDefaults.standard.set(data, forKey: userDefaultsKey)
        }
    }

    // MARK: - CloudKit Setup

    private func setupCloudKitSync() {
        // Check iCloud status and sync on launch
        Task {
            await cloudKit.checkAccountStatus()

            if cloudKit.isAvailable {
                await syncWithCloud()
                await cloudKit.subscribeToChanges()
            }
        }

        // Observe CloudKit status changes
        cloudKit.$iCloudStatus
            .dropFirst()
            .sink { [weak self] status in
                if status == .available {
                    Task {
                        await self?.syncWithCloud()
                    }
                }
            }
            .store(in: &cancellables)
    }

    // MARK: - Export/Import

    func exportFavorites() -> Data? {
        try? JSONEncoder().encode(Array(favoriteIDs))
    }

    func importFavorites(from data: Data) {
        guard let ids = try? JSONDecoder().decode([String].self, from: data) else { return }
        favoriteIDs = Set(ids)
        saveToLocal()

        Task {
            await cloudKit.saveFavorites(favoriteIDs)
        }
    }

    func clearAll() {
        favoriteIDs.removeAll()
        saveToLocal()

        Task {
            await cloudKit.saveFavorites([])
        }
    }
}


     1→import Foundation
     2→import MapKit
     3→
     4→/// Service for calculating routes using Apple's MKDirections API
     5→@MainActor
     6→class DirectionsService: ObservableObject {
     7→    @Published var route: MKRoute?
     8→    @Published var isCalculating = false
     9→    @Published var errorMessage: String?
    10→
    11→    /// Calculate route from user location to gym
    12→    func calculateRoute(
    13→        from source: CLLocationCoordinate2D,
    14→        to destination: Gym,
    15→        transportType: MKDirectionsTransportType = .automobile
    16→    ) async {
    17→        isCalculating = true
    18→        errorMessage = nil
    19→
    20→        let request = MKDirections.Request()
    21→
    22→        // Source: User's current location
    23→        let sourcePlacemark = MKPlacemark(coordinate: source)
    24→        request.source = MKMapItem(placemark: sourcePlacemark)
    25→
    26→        // Destination: Gym
    27→        if let mapItem = destination.mapItem {
    28→            request.destination = mapItem
    29→        } else {
    30→            let destPlacemark = MKPlacemark(coordinate: destination.coordinate)
    31→            let destMapItem = MKMapItem(placemark: destPlacemark)
    32→            destMapItem.name = destination.name
    33→            request.destination = destMapItem
    34→        }
    35→
    36→        request.transportType = transportType
    37→        request.requestsAlternateRoutes = false
    38→
    39→        let directions = MKDirections(request: request)
    40→
    41→        do {
    42→            let response = try await directions.calculate()
    43→            route = response.routes.first
    44→            isCalculating = false
    45→        } catch {
    46→            errorMessage = error.localizedDescription
    47→            route = nil
    48→            isCalculating = false
    49→        }
    50→    }
    51→
    52→    /// Get estimated travel time and distance
    53→    func getRouteInfo(
    54→        from source: CLLocationCoordinate2D,
    55→        to destination: Gym,
    56→        transportType: MKDirectionsTransportType = .automobile
    57→    ) async -> RouteInfo? {
    58→        let request = MKDirections.Request()
    59→
    60→        let sourcePlacemark = MKPlacemark(coordinate: source)
    61→        request.source = MKMapItem(placemark: sourcePlacemark)
    62→
    63→        if let mapItem = destination.mapItem {
    64→            request.destination = mapItem
    65→        } else {
    66→            let destPlacemark = MKPlacemark(coordinate: destination.coordinate)
    67→            request.destination = MKMapItem(placemark: destPlacemark)
    68→        }
    69→
    70→        request.transportType = transportType
    71→
    72→        let directions = MKDirections(request: request)
    73→
    74→        do {
    75→            let response = try await directions.calculate()
    76→            guard let route = response.routes.first else { return nil }
    77→
    78→            return RouteInfo(
    79→                distance: route.distance,
    80→                expectedTravelTime: route.expectedTravelTime,
    81→                transportType: transportType
    82→            )
    83→        } catch {
    84→            return nil
    85→        }
    86→    }
    87→
    88→    func clearRoute() {
    89→        route = nil
    90→    }
    91→}
    92→
    93→// MARK: - Route Info
    94→struct RouteInfo {
    95→    let distance: CLLocationDistance
    96→    let expectedTravelTime: TimeInterval
    97→    let transportType: MKDirectionsTransportType
    98→
    99→    var formattedDistance: String {
   100→        if distance < 1000 {
   101→            return "\(Int(distance)) m"
   102→        } else {
   103→            return String(format: "%.1f km", distance / 1000)
   104→        }
   105→    }
   106→
   107→    var formattedTravelTime: String {
   108→        let minutes = Int(expectedTravelTime / 60)
   109→        if minutes < 60 {
   110→            return "\(minutes) min"
   111→        } else {
   112→            let hours = minutes / 60
   113→            let remainingMinutes = minutes % 60
   114→            return "\(hours)h \(remainingMinutes)min"
   115→        }
   116→    }
   117→
   118→    var transportIcon: String {
   119→        switch transportType {
   120→        case .automobile:
   121→            return "car.fill"
   122→        case .walking:
   123→            return "figure.walk"
   124→        case .transit:
   125→            return "bus.fill"
   126→        default:
   127→            return "arrow.right"
   128→        }
   129→    }
   130→}
   131→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import MapKit

/// Service for calculating routes using Apple's MKDirections API
@MainActor
class DirectionsService: ObservableObject {
    @Published var route: MKRoute?
    @Published var isCalculating = false
    @Published var errorMessage: String?

    /// Calculate route from user location to gym
    func calculateRoute(
        from source: CLLocationCoordinate2D,
        to destination: Gym,
        transportType: MKDirectionsTransportType = .automobile
    ) async {
        isCalculating = true
        errorMessage = nil

        let request = MKDirections.Request()

        // Source: User's current location
        let sourcePlacemark = MKPlacemark(coordinate: source)
        request.source = MKMapItem(placemark: sourcePlacemark)

        // Destination: Gym
        if let mapItem = destination.mapItem {
            request.destination = mapItem
        } else {
            let destPlacemark = MKPlacemark(coordinate: destination.coordinate)
            let destMapItem = MKMapItem(placemark: destPlacemark)
            destMapItem.name = destination.name
            request.destination = destMapItem
        }

        request.transportType = transportType
        request.requestsAlternateRoutes = false

        let directions = MKDirections(request: request)

        do {
            let response = try await directions.calculate()
            route = response.routes.first
            isCalculating = false
        } catch {
            errorMessage = error.localizedDescription
            route = nil
            isCalculating = false
        }
    }

    /// Get estimated travel time and distance
    func getRouteInfo(
        from source: CLLocationCoordinate2D,
        to destination: Gym,
        transportType: MKDirectionsTransportType = .automobile
    ) async -> RouteInfo? {
        let request = MKDirections.Request()

        let sourcePlacemark = MKPlacemark(coordinate: source)
        request.source = MKMapItem(placemark: sourcePlacemark)

        if let mapItem = destination.mapItem {
            request.destination = mapItem
        } else {
            let destPlacemark = MKPlacemark(coordinate: destination.coordinate)
            request.destination = MKMapItem(placemark: destPlacemark)
        }

        request.transportType = transportType

        let directions = MKDirections(request: request)

        do {
            let response = try await directions.calculate()
            guard let route = response.routes.first else { return nil }

            return RouteInfo(
                distance: route.distance,
                expectedTravelTime: route.expectedTravelTime,
                transportType: transportType
            )
        } catch {
            return nil
        }
    }

    func clearRoute() {
        route = nil
    }
}

// MARK: - Route Info
struct RouteInfo {
    let distance: CLLocationDistance
    let expectedTravelTime: TimeInterval
    let transportType: MKDirectionsTransportType

    var formattedDistance: String {
        if distance < 1000 {
            return "\(Int(distance)) m"
        } else {
            return String(format: "%.1f km", distance / 1000)
        }
    }

    var formattedTravelTime: String {
        let minutes = Int(expectedTravelTime / 60)
        if minutes < 60 {
            return "\(minutes) min"
        } else {
            let hours = minutes / 60
            let remainingMinutes = minutes % 60
            return "\(hours)h \(remainingMinutes)min"
        }
    }

    var transportIcon: String {
        switch transportType {
        case .automobile:
            return "car.fill"
        case .walking:
            return "figure.walk"
        case .transit:
            return "bus.fill"
        default:
            return "arrow.right"
        }
    }
}


     1→import Foundation
     2→import CloudKit
     3→import Combine
     4→
     5→/// Service for syncing favorites with iCloud using CloudKit
     6→@MainActor
     7→class CloudKitService: ObservableObject {
     8→    static let shared = CloudKitService()
     9→
    10→    @Published var iCloudStatus: CKAccountStatus = .couldNotDetermine
    11→    @Published var isSyncing = false
    12→    @Published var lastSyncDate: Date?
    13→    @Published var errorMessage: String?
    14→
    15→    private let container: CKContainer
    16→    private let privateDatabase: CKDatabase
    17→    private let recordType = "FavoriteGym"
    18→    private let zoneID = CKRecordZone.ID(zoneName: "FavoritesZone", ownerName: CKCurrentUserDefaultName)
    19→
    20→    private var subscriptionID = "favorites-changes"
    21→
    22→    private init() {
    23→        // Use default container (configured in entitlements)
    24→        container = CKContainer(identifier: "iCloud.com.neog.GymFinder")
    25→        privateDatabase = container.privateCloudDatabase
    26→    }
    27→
    28→    // MARK: - Account Status
    29→
    30→    func checkAccountStatus() async {
    31→        do {
    32→            let status = try await container.accountStatus()
    33→            iCloudStatus = status
    34→        } catch {
    35→            iCloudStatus = .couldNotDetermine
    36→            errorMessage = error.localizedDescription
    37→        }
    38→    }
    39→
    40→    var isAvailable: Bool {
    41→        iCloudStatus == .available
    42→    }
    43→
    44→    var statusDescription: String {
    45→        switch iCloudStatus {
    46→        case .available:
    47→            return "Conectado"
    48→        case .noAccount:
    49→            return "Sem conta iCloud"
    50→        case .restricted:
    51→            return "Restrito"
    52→        case .couldNotDetermine:
    53→            return "Verificando..."
    54→        case .temporarilyUnavailable:
    55→            return "Temporariamente indisponível"
    56→        @unknown default:
    57→            return "Desconhecido"
    58→        }
    59→    }
    60→
    61→    // MARK: - Zone Setup
    62→
    63→    func setupZone() async throws {
    64→        let zone = CKRecordZone(zoneID: zoneID)
    65→        do {
    66→            _ = try await privateDatabase.save(zone)
    67→        } catch let error as CKError where error.code == .serverRecordChanged {
    68→            // Zone already exists, that's fine
    69→        }
    70→    }
    71→
    72→    // MARK: - Save Favorites
    73→
    74→    func saveFavorites(_ favoriteIDs: Set<String>) async {
    75→        guard isAvailable else { return }
    76→
    77→        isSyncing = true
    78→        errorMessage = nil
    79→
    80→        do {
    81→            // First, ensure zone exists
    82→            try await setupZone()
    83→
    84→            // Fetch existing records to update/delete
    85→            let existingRecords = try await fetchAllFavoriteRecords()
    86→            let existingIDs = Set(existingRecords.compactMap { $0["gymId"] as? String })
    87→
    88→            // Records to add
    89→            let idsToAdd = favoriteIDs.subtracting(existingIDs)
    90→            let recordsToSave = idsToAdd.map { gymId -> CKRecord in
    91→                let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
    92→                let record = CKRecord(recordType: recordType, recordID: recordID)
    93→                record["gymId"] = gymId
    94→                record["addedDate"] = Date()
    95→                return record
    96→            }
    97→
    98→            // Records to delete
    99→            let idsToDelete = existingIDs.subtracting(favoriteIDs)
   100→            let recordIDsToDelete = existingRecords
   101→                .filter { record in
   102→                    guard let gymId = record["gymId"] as? String else { return false }
   103→                    return idsToDelete.contains(gymId)
   104→                }
   105→                .map { $0.recordID }
   106→
   107→            // Perform modifications
   108→            if !recordsToSave.isEmpty || !recordIDsToDelete.isEmpty {
   109→                let operation = CKModifyRecordsOperation(
   110→                    recordsToSave: recordsToSave,
   111→                    recordIDsToDelete: recordIDsToDelete
   112→                )
   113→                operation.savePolicy = .changedKeys
   114→
   115→                try await privateDatabase.modifyRecords(saving: recordsToSave, deleting: recordIDsToDelete)
   116→            }
   117→
   118→            lastSyncDate = Date()
   119→            isSyncing = false
   120→
   121→        } catch {
   122→            errorMessage = error.localizedDescription
   123→            isSyncing = false
   124→        }
   125→    }
   126→
   127→    // MARK: - Fetch Favorites
   128→
   129→    func fetchFavorites() async -> Set<String> {
   130→        guard isAvailable else { return [] }
   131→
   132→        isSyncing = true
   133→        errorMessage = nil
   134→
   135→        do {
   136→            try await setupZone()
   137→            let records = try await fetchAllFavoriteRecords()
   138→            let ids = Set(records.compactMap { $0["gymId"] as? String })
   139→
   140→            lastSyncDate = Date()
   141→            isSyncing = false
   142→            return ids
   143→
   144→        } catch {
   145→            errorMessage = error.localizedDescription
   146→            isSyncing = false
   147→            return []
   148→        }
   149→    }
   150→
   151→    private func fetchAllFavoriteRecords() async throws -> [CKRecord] {
   152→        let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
   153→
   154→        var allRecords: [CKRecord] = []
   155→
   156→        let (results, _) = try await privateDatabase.records(
   157→            matching: query,
   158→            inZoneWith: zoneID,
   159→            resultsLimit: CKQueryOperation.maximumResults
   160→        )
   161→
   162→        for (_, result) in results {
   163→            if case .success(let record) = result {
   164→                allRecords.append(record)
   165→            }
   166→        }
   167→
   168→        return allRecords
   169→    }
   170→
   171→    // MARK: - Add Single Favorite
   172→
   173→    func addFavorite(_ gymId: String) async {
   174→        guard isAvailable else { return }
   175→
   176→        do {
   177→            try await setupZone()
   178→
   179→            let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
   180→            let record = CKRecord(recordType: recordType, recordID: recordID)
   181→            record["gymId"] = gymId
   182→            record["addedDate"] = Date()
   183→
   184→            _ = try await privateDatabase.save(record)
   185→
   186→        } catch let error as CKError where error.code == .serverRecordChanged {
   187→            // Record already exists
   188→        } catch {
   189→            errorMessage = error.localizedDescription
   190→        }
   191→    }
   192→
   193→    // MARK: - Remove Single Favorite
   194→
   195→    func removeFavorite(_ gymId: String) async {
   196→        guard isAvailable else { return }
   197→
   198→        do {
   199→            let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
   200→            try await privateDatabase.deleteRecord(withID: recordID)
   201→        } catch let error as CKError where error.code == .unknownItem {
   202→            // Record doesn't exist, that's fine
   203→        } catch {
   204→            errorMessage = error.localizedDescription
   205→        }
   206→    }
   207→
   208→    // MARK: - Subscribe to Changes
   209→
   210→    func subscribeToChanges() async {
   211→        guard isAvailable else { return }
   212→
   213→        do {
   214→            try await setupZone()
   215→
   216→            let subscription = CKDatabaseSubscription(subscriptionID: subscriptionID)
   217→
   218→            let notificationInfo = CKSubscription.NotificationInfo()
   219→            notificationInfo.shouldSendContentAvailable = true
   220→
   221→            subscription.notificationInfo = notificationInfo
   222→
   223→            _ = try await privateDatabase.save(subscription)
   224→
   225→        } catch let error as CKError where error.code == .serverRecordChanged {
   226→            // Subscription already exists
   227→        } catch {
   228→            print("CloudKit subscription error: \(error)")
   229→        }
   230→    }
   231→}
   232→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CloudKit
import Combine

/// Service for syncing favorites with iCloud using CloudKit
@MainActor
class CloudKitService: ObservableObject {
    static let shared = CloudKitService()

    @Published var iCloudStatus: CKAccountStatus = .couldNotDetermine
    @Published var isSyncing = false
    @Published var lastSyncDate: Date?
    @Published var errorMessage: String?

    private let container: CKContainer
    private let privateDatabase: CKDatabase
    private let recordType = "FavoriteGym"
    private let zoneID = CKRecordZone.ID(zoneName: "FavoritesZone", ownerName: CKCurrentUserDefaultName)

    private var subscriptionID = "favorites-changes"

    private init() {
        // Use default container (configured in entitlements)
        container = CKContainer(identifier: "iCloud.com.neog.GymFinder")
        privateDatabase = container.privateCloudDatabase
    }

    // MARK: - Account Status

    func checkAccountStatus() async {
        do {
            let status = try await container.accountStatus()
            iCloudStatus = status
        } catch {
            iCloudStatus = .couldNotDetermine
            errorMessage = error.localizedDescription
        }
    }

    var isAvailable: Bool {
        iCloudStatus == .available
    }

    var statusDescription: String {
        switch iCloudStatus {
        case .available:
            return "Conectado"
        case .noAccount:
            return "Sem conta iCloud"
        case .restricted:
            return "Restrito"
        case .couldNotDetermine:
            return "Verificando..."
        case .temporarilyUnavailable:
            return "Temporariamente indisponível"
        @unknown default:
            return "Desconhecido"
        }
    }

    // MARK: - Zone Setup

    func setupZone() async throws {
        let zone = CKRecordZone(zoneID: zoneID)
        do {
            _ = try await privateDatabase.save(zone)
        } catch let error as CKError where error.code == .serverRecordChanged {
            // Zone already exists, that's fine
        }
    }

    // MARK: - Save Favorites

    func saveFavorites(_ favoriteIDs: Set<String>) async {
        guard isAvailable else { return }

        isSyncing = true
        errorMessage = nil

        do {
            // First, ensure zone exists
            try await setupZone()

            // Fetch existing records to update/delete
            let existingRecords = try await fetchAllFavoriteRecords()
            let existingIDs = Set(existingRecords.compactMap { $0["gymId"] as? String })

            // Records to add
            let idsToAdd = favoriteIDs.subtracting(existingIDs)
            let recordsToSave = idsToAdd.map { gymId -> CKRecord in
                let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
                let record = CKRecord(recordType: recordType, recordID: recordID)
                record["gymId"] = gymId
                record["addedDate"] = Date()
                return record
            }

            // Records to delete
            let idsToDelete = existingIDs.subtracting(favoriteIDs)
            let recordIDsToDelete = existingRecords
                .filter { record in
                    guard let gymId = record["gymId"] as? String else { return false }
                    return idsToDelete.contains(gymId)
                }
                .map { $0.recordID }

            // Perform modifications
            if !recordsToSave.isEmpty || !recordIDsToDelete.isEmpty {
                let operation = CKModifyRecordsOperation(
                    recordsToSave: recordsToSave,
                    recordIDsToDelete: recordIDsToDelete
                )
                operation.savePolicy = .changedKeys

                try await privateDatabase.modifyRecords(saving: recordsToSave, deleting: recordIDsToDelete)
            }

            lastSyncDate = Date()
            isSyncing = false

        } catch {
            errorMessage = error.localizedDescription
            isSyncing = false
        }
    }

    // MARK: - Fetch Favorites

    func fetchFavorites() async -> Set<String> {
        guard isAvailable else { return [] }

        isSyncing = true
        errorMessage = nil

        do {
            try await setupZone()
            let records = try await fetchAllFavoriteRecords()
            let ids = Set(records.compactMap { $0["gymId"] as? String })

            lastSyncDate = Date()
            isSyncing = false
            return ids

        } catch {
            errorMessage = error.localizedDescription
            isSyncing = false
            return []
        }
    }

    private func fetchAllFavoriteRecords() async throws -> [CKRecord] {
        let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))

        var allRecords: [CKRecord] = []

        let (results, _) = try await privateDatabase.records(
            matching: query,
            inZoneWith: zoneID,
            resultsLimit: CKQueryOperation.maximumResults
        )

        for (_, result) in results {
            if case .success(let record) = result {
                allRecords.append(record)
            }
        }

        return allRecords
    }

    // MARK: - Add Single Favorite

    func addFavorite(_ gymId: String) async {
        guard isAvailable else { return }

        do {
            try await setupZone()

            let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
            let record = CKRecord(recordType: recordType, recordID: recordID)
            record["gymId"] = gymId
            record["addedDate"] = Date()

            _ = try await privateDatabase.save(record)

        } catch let error as CKError where error.code == .serverRecordChanged {
            // Record already exists
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    // MARK: - Remove Single Favorite

    func removeFavorite(_ gymId: String) async {
        guard isAvailable else { return }

        do {
            let recordID = CKRecord.ID(recordName: gymId, zoneID: zoneID)
            try await privateDatabase.deleteRecord(withID: recordID)
        } catch let error as CKError where error.code == .unknownItem {
            // Record doesn't exist, that's fine
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    // MARK: - Subscribe to Changes

    func subscribeToChanges() async {
        guard isAvailable else { return }

        do {
            try await setupZone()

            let subscription = CKDatabaseSubscription(subscriptionID: subscriptionID)

            let notificationInfo = CKSubscription.NotificationInfo()
            notificationInfo.shouldSendContentAvailable = true

            subscription.notificationInfo = notificationInfo

            _ = try await privateDatabase.save(subscription)

        } catch let error as CKError where error.code == .serverRecordChanged {
            // Subscription already exists
        } catch {
            print("CloudKit subscription error: \(error)")
        }
    }
}


Now let me read the remaining view files:


     1→import SwiftUI
     2→
     3→struct GymsListView: View {
     4→    @EnvironmentObject var locationService: LocationService
     5→    @StateObject private var viewModel = GymsViewModel()
     6→    @StateObject private var favoritesService = FavoritesService()
     7→
     8→    @State private var showingFilters = false
     9→
    10→    var body: some View {
    11→        NavigationStack {
    12→            Group {
    13→                if !viewModel.hasSearched && locationService.currentLocation == nil {
    14→                    VStack(spacing: 16) {
    15→                        ProgressView()
    16→                        Text("Aguardando localização...")
    17→                            .foregroundStyle(.secondary)
    18→                        Text("Permita acesso à localização para encontrar academias próximas")
    19→                            .font(.caption)
    20→                            .foregroundStyle(.tertiary)
    21→                            .multilineTextAlignment(.center)
    22→                            .padding(.horizontal)
    23→                    }
    24→                } else if viewModel.isLoading {
    25→                    VStack(spacing: 16) {
    26→                        ProgressView()
    27→                        Text("Buscando academias...")
    28→                            .foregroundStyle(.secondary)
    29→                    }
    30→                } else if let error = viewModel.errorMessage {
    31→                    ContentUnavailableView(
    32→                        "Erro na busca",
    33→                        systemImage: "exclamationmark.triangle",
    34→                        description: Text(error)
    35→                    )
    36→                } else if viewModel.filteredGyms.isEmpty && viewModel.hasSearched {
    37→                    ContentUnavailableView(
    38→                        "Nenhuma academia",
    39→                        systemImage: "dumbbell",
    40→                        description: Text(viewModel.filter.isActive ? "Tente ajustar os filtros" : "Não há academias na região")
    41→                    )
    42→                } else {
    43→                    List(viewModel.filteredGyms) { gym in
    44→                        NavigationLink {
    45→                            GymDetailView(gym: gym)
    46→                                .environmentObject(locationService)
    47→                        } label: {
    48→                            GymRowView(
    49→                                gym: gym,
    50→                                isFavorite: favoritesService.isFavorite(gym.id)
    51→                            )
    52→                        }
    53→                        .swipeActions(edge: .trailing) {
    54→                            Button {
    55→                                favoritesService.toggleFavorite(gym.id)
    56→                            } label: {
    57→                                Image(systemName: favoritesService.isFavorite(gym.id) ? "heart.slash" : "heart")
    58→                            }
    59→                            .tint(favoritesService.isFavorite(gym.id) ? .gray : .red)
    60→                        }
    61→                    }
    62→                    .listStyle(.plain)
    63→                    .refreshable {
    64→                        await viewModel.refresh()
    65→                    }
    66→                }
    67→            }
    68→            .navigationTitle("Academias")
    69→            .searchable(text: $viewModel.filter.searchText, prompt: "Buscar academia")
    70→            .toolbar {
    71→                ToolbarItem(placement: .topBarTrailing) {
    72→                    Button {
    73→                        showingFilters = true
    74→                    } label: {
    75→                        Image(systemName: viewModel.filter.isActive ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle")
    76→                    }
    77→                }
    78→            }
    79→            .sheet(isPresented: $showingFilters) {
    80→                FilterView(filter: $viewModel.filter)
    81→            }
    82→            .onAppear {
    83→                viewModel.setLocationService(locationService)
    84→            }
    85→            .onChange(of: viewModel.filter.maxDistance) { _, _ in
    86→                Task {
    87→                    await viewModel.searchGyms()
    88→                }
    89→            }
    90→        }
    91→    }
    92→}
    93→
    94→// MARK: - Gym Row
    95→struct GymRowView: View {
    96→    let gym: Gym
    97→    let isFavorite: Bool
    98→
    99→    var body: some View {
   100→        HStack(spacing: 12) {
   101→            // Icon
   102→            ZStack {
   103→                Circle()
   104→                    .fill(Color.orange.opacity(0.15))
   105→                    .frame(width: 50, height: 50)
   106→
   107→                Image(systemName: "dumbbell.fill")
   108→                    .font(.title3)
   109→                    .foregroundStyle(.orange)
   110→            }
   111→
   112→            VStack(alignment: .leading, spacing: 4) {
   113→                HStack {
   114→                    Text(gym.name)
   115→                        .font(.headline)
   116→                        .lineLimit(1)
   117→
   118→                    if isFavorite {
   119→                        Image(systemName: "heart.fill")
   120→                            .font(.caption)
   121→                            .foregroundStyle(.red)
   122→                    }
   123→                }
   124→
   125→                Text(gym.address)
   126→                    .font(.caption)
   127→                    .foregroundStyle(.secondary)
   128→                    .lineLimit(1)
   129→
   130→                HStack(spacing: 12) {
   131→                    // Distance
   132→                    Label(gym.formattedDistance, systemImage: "location")
   133→                        .font(.caption)
   134→                        .foregroundStyle(.blue)
   135→
   136→                    // Rating
   137→                    if let rating = gym.rating {
   138→                        HStack(spacing: 2) {
   139→                            Image(systemName: "star.fill")
   140→                                .foregroundStyle(.orange)
   141→                            Text(String(format: "%.1f", rating))
   142→                        }
   143→                        .font(.caption)
   144→                    } else {
   145→                        HStack(spacing: 2) {
   146→                            Image(systemName: "star")
   147→                                .foregroundStyle(.gray.opacity(0.5))
   148→                            Text("—")
   149→                        }
   150→                        .font(.caption)
   151→                        .foregroundStyle(.secondary)
   152→                    }
   153→
   154→                    // Phone indicator
   155→                    if gym.phone != nil {
   156→                        Image(systemName: "phone.fill")
   157→                            .font(.caption2)
   158→                            .foregroundStyle(.green)
   159→                    }
   160→
   161→                    // Website indicator
   162→                    if gym.website != nil {
   163→                        Image(systemName: "globe")
   164→                            .font(.caption2)
   165→                            .foregroundStyle(.blue)
   166→                    }
   167→                }
   168→            }
   169→
   170→            Spacer()
   171→
   172→            Image(systemName: "chevron.right")
   173→                .font(.caption)
   174→                .foregroundStyle(.tertiary)
   175→        }
   176→        .padding(.vertical, 4)
   177→    }
   178→}
   179→
   180→#Preview {
   181→    GymsListView()
   182→        .environmentObject(LocationService())
   183→}
   184→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI

struct GymsListView: View {
    @EnvironmentObject var locationService: LocationService
    @StateObject private var viewModel = GymsViewModel()
    @StateObject private var favoritesService = FavoritesService()

    @State private var showingFilters = false

    var body: some View {
        NavigationStack {
            Group {
                if !viewModel.hasSearched && locationService.currentLocation == nil {
                    VStack(spacing: 16) {
                        ProgressView()
                        Text("Aguardando localização...")
                            .foregroundStyle(.secondary)
                        Text("Permita acesso à localização para encontrar academias próximas")
                            .font(.caption)
                            .foregroundStyle(.tertiary)
                            .multilineTextAlignment(.center)
                            .padding(.horizontal)
                    }
                } else if viewModel.isLoading {
                    VStack(spacing: 16) {
                        ProgressView()
                        Text("Buscando academias...")
                            .foregroundStyle(.secondary)
                    }
                } else if let error = viewModel.errorMessage {
                    ContentUnavailableView(
                        "Erro na busca",
                        systemImage: "exclamationmark.triangle",
                        description: Text(error)
                    )
                } else if viewModel.filteredGyms.isEmpty && viewModel.hasSearched {
                    ContentUnavailableView(
                        "Nenhuma academia",
                        systemImage: "dumbbell",
                        description: Text(viewModel.filter.isActive ? "Tente ajustar os filtros" : "Não há academias na região")
                    )
                } else {
                    List(viewModel.filteredGyms) { gym in
                        NavigationLink {
                            GymDetailView(gym: gym)
                                .environmentObject(locationService)
                        } label: {
                            GymRowView(
                                gym: gym,
                                isFavorite: favoritesService.isFavorite(gym.id)
                            )
                        }
                        .swipeActions(edge: .trailing) {
                            Button {
                                favoritesService.toggleFavorite(gym.id)
                            } label: {
                                Image(systemName: favoritesService.isFavorite(gym.id) ? "heart.slash" : "heart")
                            }
                            .tint(favoritesService.isFavorite(gym.id) ? .gray : .red)
                        }
                    }
                    .listStyle(.plain)
                    .refreshable {
                        await viewModel.refresh()
                    }
                }
            }
            .navigationTitle("Academias")
            .searchable(text: $viewModel.filter.searchText, prompt: "Buscar academia")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button {
                        showingFilters = true
                    } label: {
                        Image(systemName: viewModel.filter.isActive ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle")
                    }
                }
            }
            .sheet(isPresented: $showingFilters) {
                FilterView(filter: $viewModel.filter)
            }
            .onAppear {
                viewModel.setLocationService(locationService)
            }
            .onChange(of: viewModel.filter.maxDistance) { _, _ in
                Task {
                    await viewModel.searchGyms()
                }
            }
        }
    }
}

// MARK: - Gym Row
struct GymRowView: View {
    let gym: Gym
    let isFavorite: Bool

    var body: some View {
        HStack(spacing: 12) {
            // Icon
            ZStack {
                Circle()
                    .fill(Color.orange.opacity(0.15))
                    .frame(width: 50, height: 50)

                Image(systemName: "dumbbell.fill")
                    .font(.title3)
                    .foregroundStyle(.orange)
            }

            VStack(alignment: .leading, spacing: 4) {
                HStack {
                    Text(gym.name)
                        .font(.headline)
                        .lineLimit(1)

                    if isFavorite {
                        Image(systemName: "heart.fill")
                            .font(.caption)
                            .foregroundStyle(.red)
                    }
                }

                Text(gym.address)
                    .font(.caption)
                    .foregroundStyle(.secondary)
                    .lineLimit(1)

                HStack(spacing: 12) {
                    // Distance
                    Label(gym.formattedDistance, systemImage: "location")
                        .font(.caption)
                        .foregroundStyle(.blue)

                    // Rating
                    if let rating = gym.rating {
                        HStack(spacing: 2) {
                            Image(systemName: "star.fill")
                                .foregroundStyle(.orange)
                            Text(String(format: "%.1f", rating))
                        }
                        .font(.caption)
                    } else {
                        HStack(spacing: 2) {
                            Image(systemName: "star")
                                .foregroundStyle(.gray.opacity(0.5))
                            Text("—")
                        }
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    }

                    // Phone indicator
                    if gym.phone != nil {
                        Image(systemName: "phone.fill")
                            .font(.caption2)
                            .foregroundStyle(.green)
                    }

                    // Website indicator
                    if gym.website != nil {
                        Image(systemName: "globe")
                            .font(.caption2)
                            .foregroundStyle(.blue)
                    }
                }
            }

            Spacer()

            Image(systemName: "chevron.right")
                .font(.caption)
                .foregroundStyle(.tertiary)
        }
        .padding(.vertical, 4)
    }
}

#Preview {
    GymsListView()
        .environmentObject(LocationService())
}