Compare commits

...
1 Commits
Author SHA1 Message Date
Jeroen SchweitzerandClaude Opus 4.5 a376482cd1 feat: align horizon line at 50px across all environment widgets
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m24s
- Weather/Air Quality dividers align with Sun Position horizon
- Forecast card bottoms align with same horizon line
- Unified visual rhythm across all cards

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 19:50:13 +01:00
5 changed files with 198 additions and 172 deletions
+9
View File
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.5.9] - 2026-01-08
### Changed
- **Aligned horizon line across all widgets** - Consistent visual baseline at 50px from bottom
- Weather and Air Quality dividers now align with Sun Position horizon line
- Forecast card bottoms align with the same horizon
- Creates unified visual rhythm across all environment cards
## [1.5.8] - 2026-01-08 ## [1.5.8] - 2026-01-08
### Changed ### Changed
+27 -17
View File
@@ -69,15 +69,17 @@ class AirQualityWidget extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// Content area with fixed height, content aligned to bottom // Content area with fixed height, horizon line at 50px from bottom
SizedBox( SizedBox(
height: 170, height: 170,
child: Column( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
// AQI Display // Main content positioned above horizon
Row( Positioned(
left: 0,
right: 0,
bottom: 50,
child: Row(
children: [ children: [
// AQI number with colored background // AQI number with colored background
Container( Container(
@@ -105,10 +107,10 @@ class AirQualityWidget extends StatelessWidget {
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
// Level info // Level info
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@@ -124,8 +126,7 @@ class AirQualityWidget extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
aqi.level.description, aqi.level.description,
style: style: Theme.of(context).textTheme.bodySmall?.copyWith(
Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant, color: colorScheme.onSurfaceVariant,
), ),
maxLines: 2, maxLines: 2,
@@ -136,20 +137,29 @@ class AirQualityWidget extends StatelessWidget {
), ),
], ],
), ),
),
// Pollutants // Horizon divider at fixed position
if (aqi.pollutants.isNotEmpty) ...[ if (aqi.pollutants.isNotEmpty)
const SizedBox(height: 16), Positioned(
const Divider(height: 1), left: 0,
const SizedBox(height: 12), right: 0,
Wrap( bottom: 38,
child: const Divider(height: 1),
),
// Footer below horizon
if (aqi.pollutants.isNotEmpty)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Wrap(
spacing: 16, spacing: 16,
runSpacing: 8, runSpacing: 8,
children: aqi.pollutants children: aqi.pollutants
.map((p) => _PollutantChip(pollutant: p)) .map((p) => _PollutantChip(pollutant: p))
.toList(), .toList(),
), ),
], ),
], ],
), ),
), ),
+4 -1
View File
@@ -108,9 +108,11 @@ class ForecastWidget extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// Content area with fixed height, content aligned to bottom // Content area with fixed height, card bottoms at horizon (50px from bottom)
SizedBox( SizedBox(
height: 170, height: 170,
child: Padding(
padding: const EdgeInsets.only(bottom: 50),
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: SizedBox( child: SizedBox(
@@ -126,6 +128,7 @@ class ForecastWidget extends StatelessWidget {
), ),
), ),
), ),
),
], ],
), ),
), ),
+32 -28
View File
@@ -71,17 +71,19 @@ class WeatherWidget extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// Content area with fixed height, content aligned to bottom // Content area with fixed height, horizon line at 50px from bottom
SizedBox( SizedBox(
height: 170, height: 170,
child: Column( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
// Main weather display (matching AQI layout) // Main content positioned above horizon
Row( Positioned(
left: 0,
right: 0,
bottom: 50,
child: Row(
children: [ children: [
// Weather icon in box (like AQI number box) // Weather icon in box
Container( Container(
width: 64, width: 64,
height: 64, height: 64,
@@ -104,10 +106,10 @@ class WeatherWidget extends StatelessWidget {
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
// Temperature and condition // Temperature and condition
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@@ -115,28 +117,22 @@ class WeatherWidget extends StatelessWidget {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.titleMedium .titleMedium
?.copyWith( ?.copyWith(fontWeight: FontWeight.w600),
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
weather.condition, weather.condition,
style: style: Theme.of(context).textTheme.bodySmall?.copyWith(
Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant, color: colorScheme.onSurfaceVariant,
), ),
maxLines: 2, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (weather.location.isNotEmpty) ...[ if (weather.location.isNotEmpty) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
weather.location, weather.location,
style: Theme.of(context) style: Theme.of(context).textTheme.labelSmall?.copyWith(
.textTheme
.labelSmall
?.copyWith(
color: colorScheme.outline, color: colorScheme.outline,
), ),
maxLines: 1, maxLines: 1,
@@ -148,13 +144,22 @@ class WeatherWidget extends StatelessWidget {
), ),
], ],
), ),
),
// Details // Horizon divider at fixed position
if (weather.humidity != null || weather.windSpeed != null) ...[ if (weather.humidity != null || weather.windSpeed != null)
const SizedBox(height: 16), Positioned(
const Divider(height: 1), left: 0,
const SizedBox(height: 12), right: 0,
Wrap( bottom: 38,
child: const Divider(height: 1),
),
// Footer below horizon
if (weather.humidity != null || weather.windSpeed != null)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Wrap(
spacing: 16, spacing: 16,
runSpacing: 8, runSpacing: 8,
children: [ children: [
@@ -173,12 +178,11 @@ class WeatherWidget extends StatelessWidget {
if (weather.feelsLike != null) if (weather.feelsLike != null)
_DetailChip( _DetailChip(
label: 'Feels', label: 'Feels',
value: value: '${weather.feelsLike!.round()}°${weather.unit.symbol}',
'${weather.feelsLike!.round()}°${weather.unit.symbol}',
), ),
], ],
), ),
], ),
], ],
), ),
), ),
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.5.8+1 version: 1.5.9+1
environment: environment:
sdk: ^3.10.4 sdk: ^3.10.4