import 'package:flutter/material.dart'; import 'package:tatlock_ui/features/front_hall/data/models/environment_model.dart' as api; import 'package:tatlock_ui/shared/theme/stoplight_colors.dart'; /// Air Quality Index widget displaying current AQI. /// /// Displays air quality data from the Core API environment endpoint. /// Only render this widget when air quality data is available. class AirQualityWidget extends StatelessWidget { const AirQualityWidget({ super.key, this.apiData, this.data, this.compact = false, }); /// Air quality data from API. Takes priority over legacy data. final api.AirQualityData? apiData; /// Legacy air quality data to display. Uses mock data if null. final AirQualityData? data; /// Whether to use compact layout. final bool compact; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; // Convert API data to local model, or use legacy data final aqi = apiData != null ? _fromApiData(apiData!) : (data ?? AirQualityData.mock()); if (compact) { return _buildCompact(context, colorScheme, aqi); } return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Header Row( children: [ Icon( Icons.air, size: 20, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Text( 'Air Quality', style: Theme.of(context).textTheme.titleSmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 16), // AQI Display Row( children: [ // AQI number with colored background Container( width: 64, height: 64, decoration: BoxDecoration( color: aqi.level.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(12), border: Border.all( color: aqi.level.color.withValues(alpha: 0.3), width: 2, ), ), child: Center( child: Text( '${aqi.index}', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: aqi.level.color, ), ), ), ), const SizedBox(width: 16), // Level info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( aqi.level.label, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: aqi.level.color, ), ), const SizedBox(height: 4), Text( aqi.level.description, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), // Pollutants if (aqi.pollutants.isNotEmpty) ...[ const SizedBox(height: 16), const Divider(height: 1), const SizedBox(height: 12), Wrap( spacing: 16, runSpacing: 8, children: aqi.pollutants .map((p) => _PollutantChip(pollutant: p)) .toList(), ), ], ], ), ), ); } Widget _buildCompact( BuildContext context, ColorScheme colorScheme, AirQualityData aqi, ) { return Card( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: aqi.level.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Center( child: Text( '${aqi.index}', style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: aqi.level.color, ), ), ), ), const SizedBox(width: 8), Text( 'AQI', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ), ); } } class _PollutantChip extends StatelessWidget { const _PollutantChip({required this.pollutant}); final Pollutant pollutant; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Row( mainAxisSize: MainAxisSize.min, children: [ Text( pollutant.name, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: colorScheme.outline, ), ), const SizedBox(width: 4), Text( '${pollutant.value.round()} ${pollutant.unit}', style: Theme.of(context).textTheme.bodySmall?.copyWith( fontWeight: FontWeight.w500, ), ), ], ); } } /// Converts API air quality data to local AirQualityData model. AirQualityData _fromApiData(api.AirQualityData data) { final index = data.aqi ?? 0; final pollutants = []; if (data.pm25 != null) { pollutants.add(Pollutant(name: 'PM2.5', value: data.pm25!, unit: 'µg/m³')); } if (data.pm10 != null) { pollutants.add(Pollutant(name: 'PM10', value: data.pm10!, unit: 'µg/m³')); } if (data.o3 != null) { pollutants.add(Pollutant(name: 'O₃', value: data.o3!, unit: 'ppb')); } if (data.no2 != null) { pollutants.add(Pollutant(name: 'NO₂', value: data.no2!, unit: 'ppb')); } return AirQualityData( index: index, level: AqiLevel.fromIndex(index), pollutants: pollutants, ); } /// Air Quality Index levels based on US EPA standard. enum AqiLevel { good( label: 'Good', description: 'Air quality is satisfactory', color: StoplightColors.green, minIndex: 0, maxIndex: 50, ), moderate( label: 'Moderate', description: 'Acceptable for most people', color: StoplightColors.orange, minIndex: 51, maxIndex: 100, ), unhealthySensitive( label: 'Unhealthy for Sensitive', description: 'May affect sensitive groups', color: StoplightColors.orange, minIndex: 101, maxIndex: 150, ), unhealthy( label: 'Unhealthy', description: 'Health effects for everyone', color: StoplightColors.red, minIndex: 151, maxIndex: 200, ), veryUnhealthy( label: 'Very Unhealthy', description: 'Serious health effects', color: Color(0xFF8B008B), minIndex: 201, maxIndex: 300, ), hazardous( label: 'Hazardous', description: 'Health emergency conditions', color: Color(0xFF800000), minIndex: 301, maxIndex: 500, ); const AqiLevel({ required this.label, required this.description, required this.color, required this.minIndex, required this.maxIndex, }); final String label; final String description; final Color color; final int minIndex; final int maxIndex; static AqiLevel fromIndex(int index) { for (final level in AqiLevel.values) { if (index >= level.minIndex && index <= level.maxIndex) { return level; } } return AqiLevel.hazardous; } } /// Pollutant data. class Pollutant { const Pollutant({ required this.name, required this.value, required this.unit, }); final String name; final double value; final String unit; } /// Air quality data model. class AirQualityData { const AirQualityData({ required this.index, required this.level, this.pollutants = const [], }); final int index; final AqiLevel level; final List pollutants; /// Create from index value. factory AirQualityData.fromIndex(int index, {List? pollutants}) { return AirQualityData( index: index, level: AqiLevel.fromIndex(index), pollutants: pollutants ?? const [], ); } /// Mock air quality data for development. factory AirQualityData.mock() { return const AirQualityData( index: 42, level: AqiLevel.good, pollutants: [ Pollutant(name: 'PM2.5', value: 8.5, unit: 'µg/m³'), Pollutant(name: 'PM10', value: 15, unit: 'µg/m³'), Pollutant(name: 'O₃', value: 32, unit: 'ppb'), ], ); } }