diff --git a/lib/ui/components/current_weather_card.dart b/lib/ui/components/current_weather_card.dart new file mode 100644 index 0000000..231c0d3 --- /dev/null +++ b/lib/ui/components/current_weather_card.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'max_min_temp_card.dart'; +import '../cubit/theme/theme_cubit.dart'; +import 'package:weather_app/ui/theme/font_families.dart'; +import '../model/current_weather.dart'; + +class CurrentWeatherCard extends StatelessWidget { + final CurrentWeather currentWeather; + + const CurrentWeatherCard({ + super.key, + required this.currentWeather, + }); + + @override + Widget build(BuildContext context) { + final theme = context + .watch() + .state; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 250, + height: 250, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned( + left: -15, + top: 5, + child: Container( + width: 250, + height: 250, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + Color.fromRGBO( + theme.colors.weatherIconColor.red, + theme.colors.weatherIconColor.green, + theme.colors.weatherIconColor.blue, + 0.25, + ), + Color.fromRGBO( + theme.colors.weatherIconColor.red, + theme.colors.weatherIconColor.green, + theme.colors.weatherIconColor.blue, + 0.08, + ), + Colors.transparent, + ], + stops: const [0.0, 0.3, 1.0], + ), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO( + theme.colors.weatherIconColor.red, + theme.colors.weatherIconColor.green, + theme.colors.weatherIconColor.blue, + 0.2, + ), + blurRadius: 70, + spreadRadius: 5, + offset: const Offset(-5, 5), + ), + ], + ), + ), + ), + Positioned( + top: 5, + child: SizedBox( + width: 220, + height: 200, + child: Image.asset( + currentWeather.image, + width: 220, + height: 200, + fit: BoxFit.contain, + ), + ), + ), + ], + ), + ), + + Text( + '${currentWeather.temperature}${currentWeather.unit}', + style: TextStyle( + fontFamily: urbanist, + fontSize: 64, + fontWeight: FontWeight.w600, + letterSpacing: 0.25, + color: theme.colors.temperatureColor, + height: 1.0, + ), + ), + + Text( + currentWeather.status, + style: TextStyle( + fontFamily: urbanist, + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0.25, + color: theme.colors.statusColor, + height: 1.0, + ), + ), + const SizedBox(height: 12), + + MaxMinTempCard( + highTemp: currentWeather.highTemp, + lowTemp: currentWeather.lowTemp, + unit: currentWeather.unit, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/ui/components/daily_weather.dart b/lib/ui/components/daily_weather.dart index 3ec6fc7..a11feab 100644 --- a/lib/ui/components/daily_weather.dart +++ b/lib/ui/components/daily_weather.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:weather_app/ui/model/daily_weather.dart'; -import '../../gen/assets.gen.dart'; import '../cubit/theme/theme_cubit.dart'; class NextDaysWeatherForecastTable extends StatelessWidget { @@ -81,7 +80,7 @@ class _DailyWeatherDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ _TempDisplay( - icon: Assets.images.arrowUp.path, + icon: 'assets/images/arrow_up.svg', text: '${dailyWeather.maxTemperature}°C', ), @@ -97,7 +96,7 @@ class _DailyWeatherDetails extends StatelessWidget { ), _TempDisplay( - icon: Assets.images.arrowDown.path, + icon: 'assets/images/arrow_down.svg', text: '${dailyWeather.minTemperature}°C', ), ], diff --git a/lib/ui/components/location_row.dart b/lib/ui/components/location_row.dart index 7d0cc92..8f16175 100644 --- a/lib/ui/components/location_row.dart +++ b/lib/ui/components/location_row.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../gen/assets.gen.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../cubit/theme/theme_cubit.dart'; class LocationRow extends StatelessWidget { @@ -14,7 +14,8 @@ class LocationRow extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.center, spacing: 4, children: [ - Assets.images.location.svg( + SvgPicture.asset( + 'assets/images/location.svg', colorFilter: ColorFilter.mode(theme.colors.text, BlendMode.srcIn), ), Text( diff --git a/lib/ui/components/max_min_temp_card.dart b/lib/ui/components/max_min_temp_card.dart new file mode 100644 index 0000000..b4fa781 --- /dev/null +++ b/lib/ui/components/max_min_temp_card.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:weather_app/ui/theme/font_families.dart'; +import '../cubit/theme/theme_cubit.dart'; + +class MaxMinTempCard extends StatelessWidget { + final String highTemp; + final String lowTemp; + final String unit; + + const MaxMinTempCard({ + super.key, + required this.highTemp, + required this.lowTemp, + required this.unit, + }); + + @override + Widget build(BuildContext context) { + final theme = context.watch().state; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + decoration: BoxDecoration( + color: theme.colors.maxMinCardBackground, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 12, + height: 12, + child: SvgPicture.asset( + 'assets/images/arrow_up.svg', + width: 12, + height: 12, + colorFilter: ColorFilter.mode( + theme.colors.maxMinTextColor, + BlendMode.srcIn, + ), + ), + ), + const SizedBox(width: 8), + Text( + '$highTemp$unit', + style: TextStyle( + fontFamily: urbanist, + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0.25, + color: theme.colors.maxMinTextColor, + height: 1.0, + ), + ), + const SizedBox(width: 8), + Container( + width: 1, + height: 20, + decoration: BoxDecoration( + color: theme.colors.maxMinDividerColor, + ), + ), + const SizedBox(width: 8), + SizedBox( + width: 12, + height: 12, + child: SvgPicture.asset( + 'assets/images/arrow_down.svg', + width: 12, + height: 12, + colorFilter: ColorFilter.mode( + theme.colors.maxMinTextColor, + BlendMode.srcIn, + ), + ), + ), + const SizedBox(width: 8), + Text( + '$lowTemp$unit', + style: TextStyle( + fontFamily: urbanist, + fontSize: 16, + fontWeight: FontWeight.w500, + letterSpacing: 0.25, + color: theme.colors.maxMinTextColor, + height: 1.0, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/ui/model/current_weather.dart b/lib/ui/model/current_weather.dart new file mode 100644 index 0000000..3723ba7 --- /dev/null +++ b/lib/ui/model/current_weather.dart @@ -0,0 +1,46 @@ +class CurrentWeather { + final String image; + final String temperature; + final String status; + final String highTemp; + final String lowTemp; + final String unit; + + const CurrentWeather({ + required this.image, + required this.temperature, + required this.status, + required this.highTemp, + required this.lowTemp, + this.unit = '°C', + }); + + factory CurrentWeather.sample() { + return const CurrentWeather( + image: 'assets/images/day_mainly_clear.png', + temperature: '24', + status: 'Partly cloudy', + highTemp: '32', + lowTemp: '20', + unit: '°C', + ); + } + + CurrentWeather copyWith({ + String? image, + String? temperature, + String? status, + String? highTemp, + String? lowTemp, + String? unit, + }) { + return CurrentWeather( + image: image ?? this.image, + temperature: temperature ?? this.temperature, + status: status ?? this.status, + highTemp: highTemp ?? this.highTemp, + lowTemp: lowTemp ?? this.lowTemp, + unit: unit ?? this.unit, + ); + } +} \ No newline at end of file diff --git a/lib/ui/screen/weather_screen.dart b/lib/ui/screen/weather_screen.dart index db3bdc9..ec352b9 100644 --- a/lib/ui/screen/weather_screen.dart +++ b/lib/ui/screen/weather_screen.dart @@ -3,14 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:weather_app/di/service_locator.dart'; import 'package:weather_app/domain/entity/weather.dart'; import 'package:weather_app/domain/model/location_coordinate.dart'; -import 'package:weather_app/ui/components/daily_weather.dart'; import 'package:weather_app/ui/cubit/location/location_cubit.dart'; import 'package:weather_app/ui/cubit/weather/weather_cubit.dart'; -import 'package:weather_app/ui/model/daily_weather.dart'; -import '../../gen/assets.gen.dart'; + +import '../components/current_weather_card.dart'; import '../components/location_row.dart'; import '../components/weather_details.dart'; import '../cubit/theme/theme_cubit.dart'; +import '../model/current_weather.dart'; import '../model/weather_info.dart'; class WeatherScreen extends StatelessWidget { @@ -44,7 +44,9 @@ class WeatherScreen extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is WeatherLoaded) { - context.read().updateTheme(state.weather.isDaytime); + context.read().updateTheme( + state.weather.isDaytime, + ); } }, builder: (context, state) { @@ -57,7 +59,7 @@ class WeatherScreen extends StatelessWidget { WeatherError() => _CustomLoading(), }; }, - ) + ), ), ), ), @@ -66,9 +68,7 @@ class WeatherScreen extends StatelessWidget { } class _CustomLoading extends StatelessWidget { - const _CustomLoading({ - super.key, - }); + const _CustomLoading({super.key}); @override Widget build(BuildContext context) { @@ -76,8 +76,6 @@ class _CustomLoading extends StatelessWidget { } } - - class _WeatherScreenContent extends StatelessWidget { final Weather weather; @@ -99,38 +97,46 @@ class _WeatherScreenContent extends StatelessWidget { child: LocationRow(), ), ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: CurrentWeatherCard( + currentWeather: CurrentWeather.sample(), + ), + ), + ), SliverToBoxAdapter( child: WeatherDetails( weatherInfo: [ WeatherInfo( - iconPath: Assets.images.fastWind.path, + iconPath: 'assets/images/fast_wind.svg', value: '${weather.currentDayWeatherStatus.windSpeed.speed.round()} ${weather.currentDayWeatherStatus.windSpeed.unit.name}', label: 'Wind', ), WeatherInfo( - iconPath: Assets.images.humidity.path, + iconPath: 'assets/images/humidity.svg', value: '${weather.currentDayWeatherStatus.humidity.round()}%', label: 'Humidity', ), WeatherInfo( - iconPath: Assets.images.rain.path, + iconPath: 'assets/images/rain.svg', value: '${weather.currentDayWeatherStatus.rain.round()}%', label: 'Rain', ), WeatherInfo( - iconPath: Assets.images.uv.path, + iconPath: 'assets/images/uv.svg', value: '${weather.currentDayWeatherStatus.uvIndex.round()}', label: 'UV Index', ), WeatherInfo( - iconPath: Assets.images.pressure.path, + iconPath: 'assets/images/pressure.svg', value: '${weather.currentDayWeatherStatus.pressure.pressure.round()} ${weather.currentDayWeatherStatus.pressure.unit.name}', label: 'Pressure', ), WeatherInfo( - iconPath: Assets.images.temperature.path, + iconPath: 'assets/images/temperature.svg', value: '${weather.currentDayWeatherStatus.feelsLike.temperature.round()} ${weather.currentDayWeatherStatus.feelsLike.unit.name}', label: 'Feels Like', diff --git a/lib/ui/theme/app_colors.dart b/lib/ui/theme/app_colors.dart index fd2d752..3c0c5df 100644 --- a/lib/ui/theme/app_colors.dart +++ b/lib/ui/theme/app_colors.dart @@ -28,4 +28,24 @@ abstract class AppColors { ); Color get border; + + Color get weatherIconColor; + + Color get glassmorphismBackground; + + Color get glassmorphismBorder; + + Color get cardShadow; + + Color get temperatureColor; + + Color get statusColor; + + Color get locationColor; + + Color get maxMinCardBackground; + + Color get maxMinDividerColor; + + Color get maxMinTextColor; } diff --git a/lib/ui/theme/app_colors_day.dart b/lib/ui/theme/app_colors_day.dart index a0f1f28..6b8a9ca 100644 --- a/lib/ui/theme/app_colors_day.dart +++ b/lib/ui/theme/app_colors_day.dart @@ -34,4 +34,34 @@ class DayAppColors extends AppColors { @override Color get border => const Color(0x14060414); + + @override + Color get weatherIconColor => const Color(0xFF00619D); + + @override + Color get glassmorphismBackground => const Color(0x33FFFFFF); + + @override + Color get glassmorphismBorder => const Color(0x4DFFFFFF); + + @override + Color get cardShadow => const Color(0x1A000000); + + @override + Color get temperatureColor => const Color(0xFF060414); + + @override + Color get statusColor => const Color(0x99060414); + + @override + Color get locationColor => const Color(0xFF323232); + + @override + Color get maxMinCardBackground => const Color(0x14060414); + + @override + Color get maxMinDividerColor => const Color(0x99060414); + + @override + Color get maxMinTextColor => const Color(0x99060414); } diff --git a/lib/ui/theme/app_colors_night.dart b/lib/ui/theme/app_colors_night.dart index 2751d5e..f76a6c1 100644 --- a/lib/ui/theme/app_colors_night.dart +++ b/lib/ui/theme/app_colors_night.dart @@ -34,4 +34,34 @@ class NightAppColors extends AppColors { @override Color get border => const Color(0x14FFFFFF); + + @override + Color get weatherIconColor => const Color(0xFFC0B7FF); + + @override + Color get glassmorphismBackground => const Color(0x33000000); + + @override + Color get glassmorphismBorder => const Color(0x1AFFFFFF); + + @override + Color get cardShadow => const Color(0x4D000000); + + @override + Color get temperatureColor => const Color(0xFFFFFFFF); + + @override + Color get statusColor => const Color(0x99FFFFFF); + + @override + Color get locationColor => const Color(0xFFFFFFFF); + + @override + Color get maxMinCardBackground => const Color(0x14FFFFFF); + + @override + Color get maxMinDividerColor => const Color(0xDEFFFFFF); + + @override + Color get maxMinTextColor => const Color(0xDEFFFFFF); } diff --git a/lib/ui/theme/app_theme.dart b/lib/ui/theme/app_theme.dart index 6724cf9..5c9b825 100644 --- a/lib/ui/theme/app_theme.dart +++ b/lib/ui/theme/app_theme.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:weather_app/gen/fonts.gen.dart'; import 'package:weather_app/ui/theme/app_colors.dart'; +import 'package:weather_app/ui/theme/font_families.dart'; abstract class AppTheme { final Brightness brightness; @@ -10,7 +10,7 @@ abstract class AppTheme { ThemeData get themeData { return ThemeData( - fontFamily: FontFamily.urbanist, + fontFamily: urbanist, brightness: brightness, primaryColor: colors.primary, colorScheme: ColorScheme.fromSeed( diff --git a/lib/ui/theme/font_families.dart b/lib/ui/theme/font_families.dart new file mode 100644 index 0000000..6edd670 --- /dev/null +++ b/lib/ui/theme/font_families.dart @@ -0,0 +1 @@ +const String urbanist = 'Urbanist'; \ No newline at end of file diff --git a/lib/ui/widgets/glassmorphism_container.dart b/lib/ui/widgets/glassmorphism_container.dart new file mode 100644 index 0000000..6f9f072 --- /dev/null +++ b/lib/ui/widgets/glassmorphism_container.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'dart:ui'; + +class GlassmorphismContainer extends StatelessWidget { + final Widget child; + final double blur; + final double opacity; + final double borderRadius; + final double? width; + final double? height; + final Color? backgroundColor; + final Color? borderColor; + final List? shadows; + + const GlassmorphismContainer({ + super.key, + required this.child, + this.blur = 10.0, + this.opacity = 0.15, + this.borderRadius = 30.0, + this.width, + this.height, + this.backgroundColor, + this.borderColor, + this.shadows, + }); + + @override + Widget build(BuildContext context) { + final effectiveBackgroundColor = backgroundColor ?? Colors.white.withOpacity(opacity); + final effectiveBorderColor = borderColor ?? Colors.white.withOpacity(0.3); + + return Container( + width: width, + height: height, + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Container( + decoration: BoxDecoration( + color: effectiveBackgroundColor, + borderRadius: BorderRadius.circular(borderRadius), + border: Border.all( + color: effectiveBorderColor, + width: 1.5, + ), + boxShadow: shadows, + ), + child: child, + ), + ), + ), + ); + } +} \ No newline at end of file