Skip to main content

Overview

The Pollutants Grid provides detailed monitoring of six major air pollutants, each with its own card showing current levels, status, and health information. Users can tap any pollutant to view comprehensive details in a modal sheet.

Tracked Pollutants

Breeze monitors six key air pollutants:
  1. PM2.5 - Fine Particulate Matter
  2. PM10 - Coarse Particulate Matter
  3. NO₂ - Nitrogen Dioxide
  4. SO₂ - Sulfur Dioxide
  5. O₃ - Ground-Level Ozone
  6. CO - Carbon Monoxide

Implementation

PollutantsGrid Component

The pollutants grid is implemented as a list-style layout with tappable rows:
PollutantsGrid.swift
struct PollutantsGrid: View {
    let pollutants: [PollutantReading]
    @State private var selectedPollutant: PollutantReading?
    @State private var detentSelection: PresentationDetent = .large
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            // Section Header
            HStack {
                Text("Air Quality Details")
                    .font(.title3)
                    .fontWeight(.semibold)
                
                Spacer()
            }
            
            // Pollutant Cards - Apple-style list
            VStack(spacing: 1) {
                ForEach(Array(pollutants.enumerated()), id: \.element.id) { index, pollutant in
                    PollutantRow(pollutant: pollutant)
                        .onTapGesture {
                            selectedPollutant = pollutant
                            detentSelection = .large
                        }
                        .background(Color.cardBackground)
                }
            }
            .clipShape(RoundedRectangle(cornerRadius: 12))
        }
        .sheet(item: $selectedPollutant) { pollutant in
            PollutantDetailSheet(pollutant: pollutant)
                .presentationDetents([.large, .medium], selection: $detentSelection)
                .presentationDragIndicator(.visible)
        }
    }
}

Pollutant Row Design

Each pollutant is displayed in a clean, information-dense row:
PollutantsGrid.swift
struct PollutantRow: View {
    let pollutant: PollutantReading
    
    var body: some View {
        HStack(spacing: 12) {
            // Icon with colored background
            ZStack {
                RoundedRectangle(cornerRadius: 8)
                    .fill(pollutant.status.color.opacity(0.15))
                    .frame(width: 36, height: 36)
                
                Image(systemName: pollutant.type.icon)
                    .font(.system(size: 16, weight: .medium))
                    .foregroundColor(pollutant.status.color)
            }
            
            // Name and description
            VStack(alignment: .leading, spacing: 2) {
                Text(pollutant.type.rawValue)
                    .font(.body)
                    .fontWeight(.medium)
                
                Text(pollutant.type.fullName)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            Spacer()
            
            // Value and status
            VStack(alignment: .trailing, spacing: 2) {
                HStack(alignment: .firstTextBaseline, spacing: 2) {
                    Text("\(pollutant.roundedValue)")
                        .font(.body)
                        .fontWeight(.semibold)
                    
                    Text(pollutant.type.unit)
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }
                
                Text(pollutant.status.rawValue)
                    .font(.caption)
                    .fontWeight(.medium)
                    .foregroundColor(pollutant.status.color)
            }
            
            // Chevron
            Image(systemName: "chevron.right")
                .font(.caption)
                .foregroundColor(Color(UIColor.tertiaryLabel))
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
    }
}

Pollutant Type Definitions

Each pollutant is defined with its properties in Pollutant.swift:
Pollutant.swift
enum PollutantType: String, CaseIterable {
    case pm25 = "PM2.5"
    case pm10 = "PM10"
    case no2 = "NO₂"
    case so2 = "SO₂"
    case o3 = "O₃"
    case co = "CO"
    
    var fullName: String {
        switch self {
        case .pm25: return "Fine Particulate Matter (PM2.5)"
        case .pm10: return "Coarse Particulate Matter (PM10)"
        case .no2: return "Nitrogen Dioxide (NO₂)"
        case .so2: return "Sulfur Dioxide (SO₂)"
        case .o3: return "Ground-Level Ozone (O₃)"
        case .co: return "Carbon Monoxide (CO)"
        }
    }
    
    var description: String {
        switch self {
        case .pm25: return "Tiny particles ≤2.5 micrometers that can penetrate deep into lungs and bloodstream."
        case .pm10: return "Inhalable particles ≤10 micrometers from dust, pollen, and mold. Affects respiratory system."
        case .no2: return "Reddish-brown gas from vehicle emissions and power plants. Irritates airways and reduces immunity."
        case .so2: return "Colorless gas from fossil fuel combustion. Can trigger asthma and respiratory issues."
        case .o3: return "Formed by sunlight reacting with pollutants. Harmful to lungs, especially during outdoor activities."
        case .co: return "Odorless, colorless gas from incomplete combustion. Reduces oxygen delivery to body tissues."
        }
    }
    
    var icon: String {
        switch self {
        case .pm25: return "wind"
        case .pm10: return "cloud"
        case .no2: return "car.fill"
        case .so2: return "building.2.fill"
        case .o3: return "sun.max.fill"
        case .co: return "flame.fill"
        }
    }
}

Health Thresholds

Each pollutant has defined health thresholds for good, moderate, and unhealthy levels:
  • Good: 0-12 µg/m³
  • Moderate: 12.1-35.4 µg/m³
  • Unhealthy: >35.4 µg/m³
  • Icon: wind
  • Description: Fine particulate matter that penetrates deep into lungs

Detail Sheet

Tapping a pollutant opens a comprehensive detail sheet with:

Hero Section

  • Large icon with status color background
  • Pollutant name and full chemical name
  • Current reading with large, bold numbers
  • Status badge (Good, Moderate, Unhealthy)

Information Sections

  1. About - Detailed description of the pollutant and its sources
  2. Health Ranges - Visual representation of threshold levels with color coding
PollutantsGrid.swift
struct PollutantDetailSheet: View {
    let pollutant: PollutantReading
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 24) {
                    // Hero section with icon and name
                    
                    // Current reading card
                    VStack(spacing: 12) {
                        Text("Current Level")
                            .font(.subheadline)
                            .fontWeight(.medium)
                        
                        HStack(alignment: .firstTextBaseline, spacing: 4) {
                            Text("\(pollutant.roundedValue)")
                                .font(.system(size: 56, weight: .bold, design: .rounded))
                                .foregroundColor(pollutant.status.color)
                            
                            Text(pollutant.type.unit)
                                .font(.title3)
                                .foregroundColor(.secondary)
                        }
                        
                        Text(pollutant.status.rawValue)
                            .font(.subheadline)
                            .fontWeight(.semibold)
                            .foregroundColor(.white)
                            .padding(.horizontal, 16)
                            .padding(.vertical, 8)
                            .background(pollutant.status.color)
                            .clipShape(Capsule())
                    }
                    
                    // Health ranges with color indicators
                    VStack(alignment: .leading, spacing: 12) {
                        Text("Health Ranges")
                            .font(.headline)
                        
                        VStack(spacing: 8) {
                            RangeRow(
                                label: "Good",
                                range: "0–\(Int(pollutant.type.goodLimit)) µg/m³",
                                color: .aqiGood,
                                isActive: pollutant.status == .good
                            )
                            RangeRow(
                                label: "Moderate",
                                range: "\(Int(pollutant.type.goodLimit))\(Int(pollutant.type.moderateLimit)) µg/m³",
                                color: .aqiModerate,
                                isActive: pollutant.status == .moderate
                            )
                            RangeRow(
                                label: "Unhealthy",
                                range: ">\(Int(pollutant.type.moderateLimit)) µg/m³",
                                color: .aqiUnhealthy,
                                isActive: pollutant.status == .unhealthy
                            )
                        }
                    }
                }
            }
        }
    }
}

Status Calculation

Pollutant status is calculated based on threshold values:
Pollutant.swift
struct PollutantReading: Identifiable {
    let type: PollutantType
    let value: Double
    
    var status: PollutantStatus {
        if value <= type.goodLimit {
            return .good
        } else if value <= type.moderateLimit {
            return .moderate
        } else {
            return .unhealthy
        }
    }
}

Data Integration

Pollutant data is derived from the air quality response in DashboardViewModel.swift:119-126:
self.pollutants = [
    PollutantReading(type: .pm25, value: aq.pm25),
    PollutantReading(type: .pm10, value: aq.pm10),
    PollutantReading(type: .no2, value: aq.nitrogenDioxide),
    PollutantReading(type: .so2, value: aq.sulphurDioxide),
    PollutantReading(type: .o3, value: aq.ozone),
    PollutantReading(type: .co, value: aq.carbonMonoxide)
]

File Locations

  • Component: BreezeApp/Views/Dashboard/PollutantsGrid.swift
  • Model: BreezeApp/Models/Pollutant.swift
  • Service: BreezeApp/Services/AirQualityService.swift

Build docs developers (and LLMs) love