import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../constants.dart'; import '../models/weather_models.dart'; class WeatherDetailsSheet extends StatefulWidget { final Forecast forecast; final CurrentWeather current; const WeatherDetailsSheet({ super.key, required this.forecast, required this.current, }); @override State createState() => _WeatherDetailsSheetState(); } class _WeatherDetailsSheetState extends State { late DraggableScrollableController _sheetController; @override void initState() { super.initState(); _sheetController = DraggableScrollableController(); } @override void dispose() { _sheetController.dispose(); super.dispose(); } Future _animateSheetUp() async { if (!_sheetController.isAttached) return; final currentSize = _sheetController.size; double targetSize; // Determine target size based on current position if (currentSize < 0.4) { // If small, expand to medium (0.6) targetSize = 0.6; } else if (currentSize < 0.75) { // If medium, expand to large (0.9) targetSize = 0.9; } else { // If large, collapse to small (0.2) targetSize = 0.2; } await _sheetController.animateTo( targetSize, duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic, ); } @override Widget build(BuildContext context) { return DraggableScrollableSheet( controller: _sheetController, initialChildSize: 0.2, minChildSize: 0.15, maxChildSize: 0.9, builder: (BuildContext context, ScrollController scrollController) { return ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0), child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), borderRadius: const BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), ), child: Column( children: [ // Clickable header area GestureDetector( onTap: _animateSheetUp, behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( children: [ Center( child: Container( width: 40, height: 5, decoration: BoxDecoration( color: Colors.white.withOpacity(0.4), borderRadius: BorderRadius.circular(12), ), ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.keyboard_arrow_up, color: Colors.white.withOpacity(0.7), size: 20, ), const SizedBox(width: 8), Text( 'Tap to expand', style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ], ), ), ), // Scrollable content Expanded( child: ListView( controller: scrollController, padding: const EdgeInsets.symmetric( vertical: 12, horizontal: 16, ), children: [ const SizedBox(height: 8), _buildSectionTitle('Hourly Forecast'), SizedBox( height: 140, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: widget.forecast.forecastday[0].hour.length, itemBuilder: (context, index) { final hour = widget.forecast.forecastday[0].hour[index]; if (DateTime.parse( hour.time, ).isBefore(DateTime.now())) { return const SizedBox.shrink(); } return _HourlyForecastItem(hour: hour); }, ), ), const Divider(color: Colors.white24, height: 32), _buildSectionTitle('7-Day Forecast'), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: widget.forecast.forecastday.length, itemBuilder: (context, index) { final day = widget.forecast.forecastday[index]; return _DailyForecastItem(day: day); }, ), const Divider(color: Colors.white24, height: 32), _buildSectionTitle('Current Details'), _buildDetailsGrid(), ], ), ), ], ), ), ), ); }, ); } Widget _buildSectionTitle(String title) { return Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Text( title.toUpperCase(), style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.1, ), ), ); } Widget _buildDetailsGrid() { final astro = widget.forecast.forecastday[0].astro; return GridView.count( crossAxisCount: 2, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), childAspectRatio: 2.1, mainAxisSpacing: 8, crossAxisSpacing: 8, children: [ _DetailItem( title: 'Feels Like', value: '${widget.current.feelslikeC.round()}°', ), _DetailItem(title: 'UV Index', value: '${widget.current.uv.round()}'), _DetailItem(title: 'Humidity', value: '${widget.current.humidity}%'), _DetailItem( title: 'Wind', value: '${widget.current.windKph.round()} km/h', ), _DetailItem(title: 'Sunrise', value: astro.sunrise), _DetailItem(title: 'Sunset', value: astro.sunset), ], ); } } class _HourlyForecastItem extends StatelessWidget { final Hour hour; const _HourlyForecastItem({required this.hour}); @override Widget build(BuildContext context) { final time = DateFormat.j().format(DateTime.parse(hour.time)); return Container( width: 80, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), margin: const EdgeInsets.symmetric(horizontal: 4), decoration: BoxDecoration( color: kCardBackground, borderRadius: kCardBorderRadius, ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( time, style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500, ), ), Image.network(hour.condition.icon, width: 40, height: 40), Text( '${hour.tempC.round()}°', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, ), ), ], ), ); } } class _DailyForecastItem extends StatelessWidget { final ForecastDay day; const _DailyForecastItem({required this.day}); @override Widget build(BuildContext context) { final date = DateTime.parse(day.date); final dayName = DateFormat.E().format(date); final isToday = DateFormat.yMd().format(date) == DateFormat.yMd().format(DateTime.now()); return Padding( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isToday ? 'Today' : dayName, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w500, ), ), Image.network(day.day.condition.icon, width: 30, height: 30), Text( '${day.day.dailyChanceOfRain}%', style: TextStyle( color: Colors.blue.shade200, fontSize: 16, fontWeight: FontWeight.w500, ), ), Text( 'H: ${day.day.maxtempC.round()}° L: ${day.day.mintempC.round()}°', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ); } } class _DetailItem extends StatelessWidget { final String title; final String value; const _DetailItem({required this.title, required this.value}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: kCardBackground, borderRadius: kCardBorderRadius, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( title.toUpperCase(), style: TextStyle( color: Colors.white.withOpacity(0.7), fontSize: 12, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( value, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w500, ), ), ], ), ); } }