chore: release v1.5.0
feat: add dynamic environment widgets Add live weather, sun position, and forecast widgets to Front Hall dashboard, powered by data from the Qdrant volatile collection via core-api. New widgets: - SunPositionWidget: Animated arc showing sun/moon position with gradient colors - ForecastWidget: Multi-day weather outlook - Updated WeatherWidget and AirQualityWidget to accept API data Infrastructure: - Environment datasource calling GET /tools/environment - Environment provider with 5-minute auto-refresh - Freezed models for environment data Tests: - 12 widget tests for environment section - Updated existing tests with givenEnvironment() harness method 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
ea6914b5f4
commit
fffc3d5baf
@@ -1,16 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tatlock_ui/features/front_hall/data/models/environment_model.dart'
|
||||
as api;
|
||||
|
||||
/// Weather widget displaying current conditions.
|
||||
///
|
||||
/// Currently uses mock data. Will be connected to weather API in future.
|
||||
/// Displays weather data from the Core API environment endpoint.
|
||||
class WeatherWidget extends StatelessWidget {
|
||||
const WeatherWidget({
|
||||
super.key,
|
||||
this.apiData,
|
||||
this.data,
|
||||
this.compact = false,
|
||||
});
|
||||
|
||||
/// Weather data to display. Uses mock data if null.
|
||||
/// Weather data from API. Takes priority over legacy data.
|
||||
final api.WeatherData? apiData;
|
||||
|
||||
/// Legacy weather data to display. Uses mock data if null.
|
||||
final WeatherData? data;
|
||||
|
||||
/// Whether to use compact layout.
|
||||
@@ -19,7 +25,11 @@ class WeatherWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final weather = data ?? WeatherData.mock();
|
||||
|
||||
// Convert API data to local model, or use legacy data
|
||||
final weather = apiData != null
|
||||
? _fromApiData(apiData!)
|
||||
: (data ?? WeatherData.mock());
|
||||
|
||||
if (compact) {
|
||||
return _buildCompact(context, colorScheme, weather);
|
||||
@@ -206,6 +216,60 @@ class _DetailChip extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts API weather data to local WeatherData model.
|
||||
WeatherData _fromApiData(api.WeatherData data) {
|
||||
return WeatherData(
|
||||
location: data.location ?? 'Unknown',
|
||||
temperature: data.temperature ?? 0,
|
||||
condition: data.conditions ?? 'Unknown',
|
||||
icon: _getWeatherIcon(data.conditions, data.icon),
|
||||
humidity: data.humidity,
|
||||
windSpeed: data.windSpeed,
|
||||
feelsLike: data.feelsLike,
|
||||
iconColor: _getWeatherColor(data.conditions),
|
||||
);
|
||||
}
|
||||
|
||||
/// Maps weather condition to icon.
|
||||
IconData _getWeatherIcon(String? conditions, String? iconCode) {
|
||||
final condition = conditions?.toLowerCase() ?? '';
|
||||
|
||||
if (condition.contains('clear') || condition.contains('sunny')) {
|
||||
return Icons.wb_sunny;
|
||||
} else if (condition.contains('cloud') || condition.contains('overcast')) {
|
||||
return Icons.cloud;
|
||||
} else if (condition.contains('rain') || condition.contains('drizzle')) {
|
||||
return Icons.grain;
|
||||
} else if (condition.contains('storm') || condition.contains('thunder')) {
|
||||
return Icons.thunderstorm;
|
||||
} else if (condition.contains('snow') || condition.contains('sleet')) {
|
||||
return Icons.ac_unit;
|
||||
} else if (condition.contains('fog') || condition.contains('mist')) {
|
||||
return Icons.blur_on;
|
||||
} else if (condition.contains('wind')) {
|
||||
return Icons.air;
|
||||
}
|
||||
return Icons.cloud;
|
||||
}
|
||||
|
||||
/// Maps weather condition to color.
|
||||
Color? _getWeatherColor(String? conditions) {
|
||||
final condition = conditions?.toLowerCase() ?? '';
|
||||
|
||||
if (condition.contains('clear') || condition.contains('sunny')) {
|
||||
return Colors.amber;
|
||||
} else if (condition.contains('cloud')) {
|
||||
return Colors.blueGrey;
|
||||
} else if (condition.contains('rain')) {
|
||||
return Colors.blue;
|
||||
} else if (condition.contains('storm')) {
|
||||
return Colors.deepPurple;
|
||||
} else if (condition.contains('snow')) {
|
||||
return Colors.lightBlue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Temperature unit for weather display.
|
||||
enum TemperatureUnit {
|
||||
celsius('C'),
|
||||
|
||||
Reference in New Issue
Block a user