feat: environment widgets layout redesign with always-visible widgets
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m11s

- Desktop: 4-in-a-row layout (30/20/20/30 distribution)
- Tablet: 2x2 grid layout
- Mobile: Stacked vertically
- All widgets show "No data available" state instead of being hidden
- Sun position calculates from clock, defaults to 6am/6pm
- Environment refresh changed from 5min to 1 hour

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-07 11:21:48 +01:00
co-authored by Claude Opus 4.5
parent fffc3d5baf
commit d200cad8be
9 changed files with 276 additions and 45 deletions
@@ -34,9 +34,9 @@ class _DashboardContentState extends ConsumerState<DashboardContent> {
const Duration(seconds: 30),
(_) => ref.invalidate(systemStatsProvider),
);
// Refresh environment data every 5 minutes
// Refresh environment data every hour
_environmentTimer = Timer.periodic(
const Duration(minutes: 5),
const Duration(hours: 1),
(_) => ref.invalidate(environmentProvider),
);
}
@@ -279,7 +279,12 @@ class _SystemStatsCard extends StatelessWidget {
}
}
/// Environment section displaying sun position, weather, forecast, and air quality.
/// Environment section displaying sun position, weather, air quality, and forecast.
///
/// Layout:
/// - Desktop (>900px): 4 widgets in a row - Sun(30%) | Weather(20%) | AirQuality(20%) | Forecast(30%)
/// - Tablet (600-900px): 2x2 grid
/// - Mobile (<600px): Stacked vertically
class _EnvironmentSection extends StatelessWidget {
const _EnvironmentSection({
required this.envData,
@@ -293,40 +298,73 @@ class _EnvironmentSection extends StatelessWidget {
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isWide = constraints.maxWidth > 600;
return Column(
children: [
// Sun Position Widget - prominent at top
SunPositionWidget(sunTimes: envData.sunTimes),
const SizedBox(height: 12),
// Weather and Forecast side-by-side on wide screens
if (isWide)
if (constraints.maxWidth > 900) {
// Desktop: 4 in a row (30/20/20/30) - wider widgets on outsides
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 30,
child: SunPositionWidget(sunTimes: envData.sunTimes),
),
const SizedBox(width: 12),
Expanded(
flex: 20,
child: WeatherWidget(apiData: envData.weather),
),
const SizedBox(width: 12),
Expanded(
flex: 20,
child: AirQualityWidget(apiData: envData.airQuality),
),
const SizedBox(width: 12),
Expanded(
flex: 30,
child: ForecastWidget(forecast: envData.forecast),
),
],
);
} else if (constraints.maxWidth > 600) {
// Tablet: 2x2 grid
return Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: SunPositionWidget(sunTimes: envData.sunTimes),
),
const SizedBox(width: 12),
Expanded(child: WeatherWidget(apiData: envData.weather)),
],
),
const SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: AirQualityWidget(apiData: envData.airQuality),
),
const SizedBox(width: 12),
Expanded(child: ForecastWidget(forecast: envData.forecast)),
],
)
else
Column(
children: [
WeatherWidget(apiData: envData.weather),
const SizedBox(height: 12),
ForecastWidget(forecast: envData.forecast),
],
),
// Air Quality - only show if data is available
if (envData.airQuality != null) ...[
],
);
} else {
// Mobile: stacked vertically
return Column(
children: [
SunPositionWidget(sunTimes: envData.sunTimes),
const SizedBox(height: 12),
WeatherWidget(apiData: envData.weather),
const SizedBox(height: 12),
AirQualityWidget(apiData: envData.airQuality),
const SizedBox(height: 12),
ForecastWidget(forecast: envData.forecast),
],
],
);
);
}
},
);
}