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>
92 lines
2.5 KiB
Dart
92 lines
2.5 KiB
Dart
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
part 'environment_model.freezed.dart';
|
|
part 'environment_model.g.dart';
|
|
|
|
/// Environment data response from Core API.
|
|
/// Contains weather, forecast, sun times, and optionally air quality.
|
|
@freezed
|
|
sealed class EnvironmentData with _$EnvironmentData {
|
|
const factory EnvironmentData({
|
|
WeatherData? weather,
|
|
List<ForecastDay>? forecast,
|
|
@JsonKey(name: 'sun_times') SunTimesData? sunTimes,
|
|
@JsonKey(name: 'air_quality') AirQualityData? airQuality,
|
|
@JsonKey(name: 'updated_at') required DateTime updatedAt,
|
|
String? user,
|
|
}) = _EnvironmentData;
|
|
|
|
factory EnvironmentData.fromJson(Map<String, dynamic> json) =>
|
|
_$EnvironmentDataFromJson(json);
|
|
}
|
|
|
|
/// Current weather conditions.
|
|
@freezed
|
|
sealed class WeatherData with _$WeatherData {
|
|
const factory WeatherData({
|
|
double? temperature,
|
|
@JsonKey(name: 'feels_like') double? feelsLike,
|
|
String? conditions,
|
|
int? humidity,
|
|
@JsonKey(name: 'wind_speed') double? windSpeed,
|
|
@JsonKey(name: 'wind_direction') String? windDirection,
|
|
double? pressure,
|
|
double? visibility,
|
|
@JsonKey(name: 'uv_index') double? uvIndex,
|
|
String? location,
|
|
String? icon,
|
|
}) = _WeatherData;
|
|
|
|
factory WeatherData.fromJson(Map<String, dynamic> json) =>
|
|
_$WeatherDataFromJson(json);
|
|
}
|
|
|
|
/// Single day forecast data.
|
|
@freezed
|
|
sealed class ForecastDay with _$ForecastDay {
|
|
const factory ForecastDay({
|
|
required String date,
|
|
double? high,
|
|
double? low,
|
|
String? conditions,
|
|
@JsonKey(name: 'precipitation_chance') int? precipitationChance,
|
|
String? icon,
|
|
}) = _ForecastDay;
|
|
|
|
factory ForecastDay.fromJson(Map<String, dynamic> json) =>
|
|
_$ForecastDayFromJson(json);
|
|
}
|
|
|
|
/// Sunrise and sunset times.
|
|
@freezed
|
|
sealed class SunTimesData with _$SunTimesData {
|
|
const factory SunTimesData({
|
|
DateTime? sunrise,
|
|
DateTime? sunset,
|
|
@JsonKey(name: 'daylight_minutes') int? daylightMinutes,
|
|
@JsonKey(name: 'solar_noon') DateTime? solarNoon,
|
|
DateTime? dawn,
|
|
DateTime? dusk,
|
|
}) = _SunTimesData;
|
|
|
|
factory SunTimesData.fromJson(Map<String, dynamic> json) =>
|
|
_$SunTimesDataFromJson(json);
|
|
}
|
|
|
|
/// Air quality information.
|
|
@freezed
|
|
sealed class AirQualityData with _$AirQualityData {
|
|
const factory AirQualityData({
|
|
int? aqi,
|
|
String? quality,
|
|
double? pm25,
|
|
double? pm10,
|
|
double? o3,
|
|
double? no2,
|
|
String? location,
|
|
}) = _AirQualityData;
|
|
|
|
factory AirQualityData.fromJson(Map<String, dynamic> json) =>
|
|
_$AirQualityDataFromJson(json);
|
|
}
|