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 Pollen Tracking feature provides allergy sufferers with detailed information about current pollen levels from various plants and pollen types. Powered by Google’s Pollen API, it displays data in an easy-to-understand grid format with detailed plant information.
Implementation
PollenView Component
The pollen tracker uses a two-column grid layout to display pollen items:
struct PollenView: View {
let items: [PollenItem]
@State private var selectedPollen: PollenItem?
private let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "leaf.fill")
.foregroundColor(.green)
Text("Allergy Tracker")
.font(.headline)
Spacer()
Text("Powered by Google")
.font(.caption2)
.foregroundColor(.secondary)
}
LazyVGrid(columns: columns, spacing: 8) {
ForEach(items) { item in
PollenItemCard(item: item)
.onTapGesture {
selectedPollen = item
}
}
}
}
.sheet(item: $selectedPollen) { pollen in
PollenDetailSheet(pollen: pollen)
.presentationDetents([.large, .medium])
.presentationDragIndicator(.visible)
}
}
}
Pollen Item Card
Each pollen type is displayed in a compact card with:
- Pollen name
- Current level (0-5 scale)
- Visual progress bar
- Status text (None, Low, Moderate, High, Very High)
- Color-coded indicators
struct PollenItemCard: View {
let item: PollenItem
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(item.name)
.font(.subheadline)
.fontWeight(.medium)
Spacer()
HStack(spacing: 4) {
Text("\(item.value)")
.font(.title3)
.fontWeight(.bold)
.foregroundColor(item.level.color)
Image(systemName: "info.circle")
.font(.caption)
.foregroundColor(.accentColor)
}
}
// Progress bar (0-5 scale)
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 4)
.fill(Color.secondary.opacity(0.2))
.frame(height: 6)
RoundedRectangle(cornerRadius: 4)
.fill(item.level.color)
.frame(width: geometry.size.width * CGFloat(item.value) / 5.0, height: 6)
}
}
.frame(height: 6)
Text(item.level.rawValue)
.font(.caption)
.foregroundColor(.secondary)
}
.padding(10)
.background(Color.cardBackground)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
Pollen Levels
Pollen is measured on a 0-5 scale with corresponding color coding:
None (0)
Color: Green (#34c759)No pollen detected in the air
Low (1)
Color: Green (#34c759)Minimal pollen levels, unlikely to cause symptoms
Moderate (2-3)
Color: Orange (#ff9500)Noticeable pollen levels, sensitive individuals may experience symptoms
High (4)
Color: Red (#ff3b30)High pollen concentration, most allergy sufferers will be affected
Very High (5)
Color: Maroon (#8e0000)Extreme pollen levels, severe symptoms likely for those with allergies
Level Calculation
Pollen levels are determined in PollenData.swift:
struct PollenItem: Identifiable {
let id: String
let name: String
let value: Int
let category: String
let isPlant: Bool
var level: PollenLevel {
switch value {
case 0:
return .none
case 1:
return .low
case 2...3:
return .moderate
case 4:
return .high
case 5...:
return .veryHigh
default:
return .low
}
}
}
enum PollenLevel: String {
case none = "None"
case low = "Low"
case moderate = "Moderate"
case high = "High"
case veryHigh = "Very High"
}
Pollen Types Tracked
The app tracks both general pollen categories and specific plant pollens:
General Categories
- Grass - All grass pollens
- Tree - Tree pollens (various species)
- Weed - Weed pollens
Specific Plants
Examples include:
- Ragweed
- Oak
- Birch
- Pine
- Cedar
- And many more…
Detail Sheet
Tapping a pollen item opens a detailed view with comprehensive information:
struct PollenDetailSheet: View {
let pollen: PollenItem
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 20) {
// Hero section with icon and level
VStack(spacing: 12) {
ZStack {
Circle()
.fill(pollen.level.color.opacity(0.15))
.frame(width: 70, height: 70)
Image(systemName: "leaf.fill")
.font(.system(size: 28, weight: .medium))
.foregroundColor(pollen.level.color)
}
Text(pollen.name)
.font(.title2)
.fontWeight(.bold)
}
// Current level with large number
VStack(spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text("\(pollen.value)")
.font(.system(size: 44, weight: .bold, design: .rounded))
.foregroundColor(pollen.level.color)
Text("/ 5")
.font(.title3)
.foregroundColor(.secondary)
}
Text(pollen.level.rawValue)
.font(.caption)
.foregroundColor(.white)
.padding(.horizontal, 14)
.padding(.vertical, 6)
.background(pollen.level.color)
.clipShape(Capsule())
}
// Plant image (if available)
if let imageUrl = pollen.imageUrl, let url = URL(string: imageUrl) {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity)
.frame(height: 260)
.clipShape(RoundedRectangle(cornerRadius: 16))
default:
EmptyView()
}
}
}
// Plant information
if pollen.family != nil || pollen.season != nil {
VStack(alignment: .leading, spacing: 12) {
Text("About this Plant")
.font(.headline)
if let family = pollen.family {
VStack(alignment: .leading, spacing: 4) {
Text("Family")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
Text(family)
.font(.subheadline)
}
}
if let season = pollen.season {
VStack(alignment: .leading, spacing: 4) {
Text("Season")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
Text(season)
.font(.subheadline)
}
}
}
}
// Health recommendations
if let recommendations = pollen.healthRecommendations {
VStack(alignment: .leading, spacing: 12) {
Text("Health Recommendations")
.font(.headline)
VStack(alignment: .leading, spacing: 10) {
ForEach(recommendations, id: \.self) { recommendation in
HStack(alignment: .top, spacing: 10) {
Circle()
.fill(pollen.level.color.opacity(0.8))
.frame(width: 6, height: 6)
.padding(.top, 6)
Text(recommendation)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
}
}
}
}
}
}
}
Data Structure
Pollen data is structured with comprehensive plant information:
struct PollenItem: Identifiable {
let id: String
let name: String
let value: Int // 0-5 scale
let category: String
let isPlant: Bool // true for specific plants, false for general types
let imageUrl: String? // Plant photo from Google
let family: String? // Plant family
let season: String? // Peak pollen season
let appearance: String? // Plant appearance description
let healthRecommendations: [String]? // Health tips
}
Google Pollen API Integration
Data is fetched from Google’s Pollen API through PollenService.swift:
- Provides daily pollen forecasts
- Includes both general pollen types and specific plant species
- Returns health recommendations
- Provides plant images and descriptions
- Available for locations worldwide
Color Coding
Pollen level colors are defined in Color+Theme.swift:
extension Color {
static let levelNone = Color(hex: "34c759") // Green
static let levelLow = Color(hex: "34c759") // Green
static let levelModerate = Color(hex: "ff9500") // Orange
static let levelHigh = Color(hex: "ff3b30") // Red
static let levelExtreme = Color(hex: "8e0000") // Maroon
}
extension PollenLevel {
var color: Color {
switch self {
case .none: return .levelNone
case .low: return .levelLow
case .moderate: return .levelModerate
case .high: return .levelHigh
case .veryHigh: return .levelExtreme
}
}
}
Conditional Display
The pollen section only appears when data is available:
if !viewModel.pollenItems.isEmpty {
PollenView(items: viewModel.pollenItems)
}
This prevents showing an empty section when pollen data is unavailable for a location.
File Locations
- Component:
BreezeApp/Views/Environmental/PollenView.swift
- Model:
BreezeApp/Models/PollenData.swift
- Service:
BreezeApp/Services/PollenService.swift
- Colors:
BreezeApp/Extensions/Color+Theme.swift