Documentation Index
Fetch the complete documentation index at: https://mintlify.com/japsinghx/breeze-ios/llms.txt
Use this file to discover all available pages before exploring further.
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:
- PM2.5 - Fine Particulate Matter
- PM10 - Coarse Particulate Matter
- NO₂ - Nitrogen Dioxide
- SO₂ - Sulfur Dioxide
- O₃ - Ground-Level Ozone
- CO - Carbon Monoxide
Implementation
PollutantsGrid Component
The pollutants grid is implemented as a list-style layout with tappable rows:
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:
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:
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
- Good: 0-54 µg/m³
- Moderate: 54.1-154 µg/m³
- Unhealthy: >154 µg/m³
- Icon: cloud
- Description: Coarse particles from dust, pollen, and mold
- Good: 0-53 µg/m³
- Moderate: 53.1-100 µg/m³
- Unhealthy: >100 µg/m³
- Icon: car.fill
- Description: Gas from vehicle emissions and power plants
- Good: 0-35 µg/m³
- Moderate: 35.1-75 µg/m³
- Unhealthy: >75 µg/m³
- Icon: building.2.fill
- Description: Gas from fossil fuel combustion
- Good: 0-54 µg/m³
- Moderate: 54.1-70 µg/m³
- Unhealthy: >70 µg/m³
- Icon: sun.max.fill
- Description: Ground-level ozone formed by sunlight
- Good: 0-4400 µg/m³
- Moderate: 4400.1-9400 µg/m³
- Unhealthy: >9400 µg/m³
- Icon: flame.fill
- Description: Odorless gas from incomplete combustion
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)
- About - Detailed description of the pollutant and its sources
- Health Ranges - Visual representation of threshold levels with color coding
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:
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