import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/weather_provider.dart'; import '../../models/weather.dart'; import 'weather_search_screen.dart'; import 'dart:math' as math; class WeatherScreen extends StatefulWidget { const WeatherScreen({super.key}); @override State createState() => _WeatherScreenState(); } class _WeatherScreenState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().refreshWeather(); }); } // 获取动态背景渐变 List _getBackgroundGradient(Color baseColor) { // 基础色调整:确保背景不会太亮,保持深色模式的高级感 final darkBase = HSLColor.fromColor(baseColor) .withLightness(0.15) .withSaturation(0.6) .toColor(); final lightBase = HSLColor.fromColor(baseColor) .withLightness(0.3) .withSaturation(0.5) .toColor(); return [ darkBase, // 顶部深色 lightBase, // 底部稍亮 Color(0xFF000000), // 最底部纯黑,衔接自然 ]; } @override Widget build(BuildContext context) { return Consumer( builder: (context, weatherProvider, child) { final weather = weatherProvider.currentWeather; final baseColor = weather != null ? weatherProvider.getWeatherColor(weather.condition) : Colors.blueGrey; return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: _getBackgroundGradient(baseColor), begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: Stack( children: [ _buildAnimatedBackground(baseColor), CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ _buildSliverAppBar(context, weatherProvider, baseColor), SliverToBoxAdapter(child: _buildMainContent(weatherProvider)), ], ), ], ), ), ); }, ); } Widget _buildSliverAppBar( BuildContext context, WeatherProvider weatherProvider, Color baseColor, ) { return SliverAppBar( expandedHeight: 100, floating: false, pinned: true, backgroundColor: Colors.transparent, elevation: 0, flexibleSpace: ClipRRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: FlexibleSpaceBar( titlePadding: const EdgeInsets.only(left: 20, bottom: 16), title: const Text( 'Weather', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ), ), ), actions: [ _buildGlassIconButton( icon: Icons.search, onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const WeatherSearchScreen(), ), ); }, ), const SizedBox(width: 8), _buildGlassIconButton( icon: weatherProvider.isLoading ? null : Icons.refresh, isLoading: weatherProvider.isLoading, onTap: () => weatherProvider.refreshWeather(), ), const SizedBox(width: 16), ], ); } Widget _buildGlassIconButton({ IconData? icon, VoidCallback? onTap, bool isLoading = false, }) { return Container( margin: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(14), border: Border.all(color: Colors.white.withValues(alpha: 0.2), width: 0.5), ), child: IconButton( icon: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Icon(icon, color: Colors.white, size: 22), onPressed: onTap, ), ); } Widget _buildAnimatedBackground(Color color) { return Stack( children: [ ...List.generate(6, (index) { return Positioned( left: (index * 70.0) % 350, top: (index * 50.0) % 200, child: TweenAnimationBuilder( duration: Duration(milliseconds: 3000 + (index * 500)), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.translate( offset: Offset( math.sin(value * 2 * math.pi) * 30, math.cos(value * 2 * math.pi) * 20, ), child: Container( width: 100 + (index * 20.0), height: 100 + (index * 20.0), decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ color.withValues(alpha: 0.3), color.withValues(alpha: 0.0), ], ), ), ), ); }, onEnd: () { if (mounted) setState(() {}); }, ), ); }), ], ); } Widget _buildMainContent(WeatherProvider weatherProvider) { if (weatherProvider.isLoading && weatherProvider.currentWeather == null) { return _buildLoadingState(); } if (weatherProvider.error != null) { return _buildErrorState(weatherProvider); } final weather = weatherProvider.currentWeather; if (weather == null) { return _buildEmptyState(); } return Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), _buildMainWeatherDisplay(weather, weatherProvider), const SizedBox(height: 32), _buildGlassSection( title: 'Details', icon: Icons.tune, child: _buildDetailGrid(weather), ), const SizedBox(height: 24), if (weatherProvider.forecast.isNotEmpty) ...[ _buildGlassSection( title: '7-Day Forecast', icon: Icons.calendar_month_outlined, child: Column( children: weatherProvider.forecast .map((day) => _buildForecastItem(day, weatherProvider)) .toList(), ), ), const SizedBox(height: 24), ], if (weatherProvider.favoriteLocations.isNotEmpty) _buildGlassSection( title: 'Favorites', icon: Icons.bookmark_border, child: Column( children: weatherProvider.favoriteLocations .map((loc) => _buildFavoriteItem(loc, weatherProvider)) .toList(), ), ), ], ), ); } // 主天气显示 - 更加简洁大气 Widget _buildMainWeatherDisplay(Weather weather, WeatherProvider provider) { return Column( children: [ // 地点 Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.location_on, color: Colors.white70, size: 18), const SizedBox(width: 8), Flexible( child: Text.rich( TextSpan( children: [ TextSpan( text: weather.location, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, shadows: [Shadow(color: Colors.black26, blurRadius: 10)], ), ), TextSpan( text: ', ${weather.country}', style: const TextStyle( color: Colors.white70, fontSize: 18, ), ), ], ), overflow: TextOverflow.ellipsis, maxLines: 1, textAlign: TextAlign.center, ), ), ], ), ), const SizedBox(height: 24), // 图标和温度 Icon( provider.getWeatherIcon(weather.condition), color: Colors.white, size: 80, shadows: [BoxShadow(color: Colors.black26, blurRadius: 20)], ), const SizedBox(height: 16), Stack( children: [ Text( '${weather.temperature.round()}°', style: const TextStyle( color: Colors.white, fontSize: 96, fontWeight: FontWeight.w200, height: 1.0, shadows: [Shadow(color: Colors.black26, blurRadius: 10)], ), ), ], ), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white.withValues(alpha: 0.1)), ), child: Text( weather.condition, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500, letterSpacing: 0.5, ), ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Feels like ${weather.feelsLike.round()}°', style: TextStyle(color: Colors.white.withValues(alpha: 0.8)), ), Container( margin: const EdgeInsets.symmetric(horizontal: 12), width: 1, height: 16, color: Colors.white30, ), IconButton( icon: Icon( provider.isFavorite(weather.location) ? Icons.favorite : Icons.favorite_border, color: provider.isFavorite(weather.location) ? Colors.pinkAccent : Colors.white70, size: 24, ), onPressed: () { if (provider.isFavorite(weather.location)) { provider.removeFromFavorites(weather.location); } else { provider.addToFavorites(weather.location); } }, ), ], ), ], ); } // 通用毛玻璃容器组件 Widget _buildGlassSection({ required String title, required IconData icon, required Widget child, }) { return ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white.withValues(alpha: 0.1), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, color: Colors.white70, size: 20), const SizedBox(width: 8), Text( title.toUpperCase(), style: const TextStyle( color: Colors.white70, fontSize: 12, fontWeight: FontWeight.bold, letterSpacing: 1.0, ), ), ], ), const SizedBox(height: 16), child, ], ), ), ), ); } Widget _buildDetailGrid(Weather weather) { return GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 3, childAspectRatio: 1.1, crossAxisSpacing: 10, mainAxisSpacing: 10, children: [ _buildGridItem(Icons.water_drop_outlined, '${weather.humidity}%', 'Humidity'), _buildGridItem(Icons.air, '${weather.windSpeed} km/h', 'Wind'), _buildGridItem(Icons.speed, '${weather.pressure.round()} hPa', 'Pressure'), _buildGridItem(Icons.visibility_outlined, '${weather.visibility} km', 'Visibility'), _buildGridItem(Icons.wb_sunny_outlined, '${weather.uvIndex}', 'UV Index'), _buildGridItem(Icons.explore_outlined, weather.windDirection, 'Direction'), ], ); } Widget _buildGridItem(IconData icon, String value, String label) { return Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(16), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: Colors.white70, size: 22), const SizedBox(height: 8), Text( value, style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 2), Text( label, style: TextStyle( color: Colors.white.withValues(alpha: 0.5), fontSize: 10, ), ), ], ), ); } Widget _buildForecastItem(WeatherForecast forecast, WeatherProvider provider) { final date = DateTime.parse(forecast.date); final dayName = _getDayName(date); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), child: Row( children: [ Expanded( flex: 2, child: Text( dayName, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w500, fontSize: 15, ), ), ), Expanded( flex: 3, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon( provider.getWeatherIcon(forecast.condition), color: Colors.white70, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( forecast.condition, style: TextStyle( color: Colors.white.withValues(alpha: 0.6), fontSize: 13, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), Row( children: [ Text( '${forecast.maxTemp.round()}°', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15, ), ), const SizedBox(width: 10), Text( '${forecast.minTemp.round()}°', style: TextStyle( color: Colors.white.withValues(alpha: 0.4), fontSize: 15, ), ), ], ), ], ), ); } Widget _buildFavoriteItem(String location, WeatherProvider provider) { return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(12), ), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 0), leading: const Icon(Icons.location_city, color: Colors.white70), title: Text( location, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500), ), trailing: IconButton( icon: Icon( Icons.close, color: Colors.white.withValues(alpha: 0.4), size: 20, ), onPressed: () => provider.removeFromFavorites(location), ), onTap: () => provider.getWeatherByCity(location), ), ); } Widget _buildLoadingState() { return SizedBox( height: MediaQuery.of(context).size.height * 0.7, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(color: Colors.white), const SizedBox(height: 20), Text( 'Fetching weather...', style: TextStyle(color: Colors.white.withValues(alpha: 0.7)), ), ], ), ), ); } Widget _buildErrorState(WeatherProvider weatherProvider) { return Center( child: Container( margin: const EdgeInsets.only(top: 100, left: 24, right: 24), padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.red.withValues(alpha: 0.3)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_off, size: 64, color: Colors.white38), const SizedBox(height: 16), Text( weatherProvider.error!, style: const TextStyle(color: Colors.white, fontSize: 16), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: () { weatherProvider.clearError(); weatherProvider.refreshWeather(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.black, shape: const StadiumBorder(), padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), ), child: const Text('Try Again'), ), ], ), ), ); } Widget _buildEmptyState() { return SizedBox( height: MediaQuery.of(context).size.height * 0.6, child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.search, size: 64, color: Colors.white24), SizedBox(height: 16), Text( 'Search for a city to start', style: TextStyle(color: Colors.white54, fontSize: 18), ), ], ), ), ); } String _getDayName(DateTime date) { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final targetDate = DateTime(date.year, date.month, date.day); final difference = targetDate.difference(today).inDays; if (difference == 0) return 'Today'; if (difference == 1) return 'Tomorrow'; const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; return weekdays[date.weekday - 1]; } }