diff --git a/$PROFILE.txt b/$PROFILE.txt new file mode 100644 index 0000000..70755cb --- /dev/null +++ b/$PROFILE.txt @@ -0,0 +1,6 @@ +Set-Alias fbar "flutter build apk --release" +Set-Alias fbad "flutter build apk --debug" +Set-Alias fr "flutter run" +Set-Alias fbr "flutter pub run build_runner build --delete-conflicting-outputs" +Set-Alias fpg "flutter pub get" +Set-Alias fc "flutter clean" diff --git a/.env b/.env index 2b1c798..9f332a7 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -API_KEY="31QfpYJrZQMyc1DDy2yP2T:5F5a6rC1re42ZICQOrQo8Y" \ No newline at end of file +API_KEY=31QfpYJrZQMyc1DDy2yP2T:5F5a6rC1re42ZICQOrQo8Y \ No newline at end of file diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml new file mode 100644 index 0000000..fe39f70 --- /dev/null +++ b/.github/workflows/flutter_ci.yml @@ -0,0 +1,31 @@ +name: Flutter CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + - run: flutter --version + + - name: Install dependencies + run: flutter pub get + + - name: Run Build Runner + run: dart run build_runner build --delete-conflicting-outputs + + - name: Run Flutter Analyze + run: flutter analyze --no-pub --fatal-infos --fatal-warnings + diff --git a/README.md b/README.md index e364a20..de0dc9d 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Flutter ile geliştirilmiş modern hava durumu uygulaması. > Görselleri `assets/img/` klasörüne ekleyebilirsin. > Aşağıdaki tablo örnek olarak hazırlanmıştır. -| 🌤️ Anasayfa | 🔍 Şehir Arama | 📊 Detay Sayfası | -|-------------|----------------|-----------------| +| 🌤️ Anasayfa | 🔍 Şehir Arama | 📊 Detay Sayfası | 📊 Şehir Seçme Sayfası | +|-------------|----------------|-----------------|-----------------| | ![home](assets/screenshots/home_screen.jpg) | ![filter](assets/screenshots/filter_screen.jpg) | ![drawable](assets/screenshots/drawable_screen.jpg) | ![city](assets/screenshots/sehir.jpg) | --- diff --git a/flutter_01.png b/flutter_01.png new file mode 100644 index 0000000..f31ea04 Binary files /dev/null and b/flutter_01.png differ diff --git a/lib/common/bottom_app_navigation.dart b/lib/common/bottom_app_navigation.dart index c5c35dc..dfc6d94 100644 --- a/lib/common/bottom_app_navigation.dart +++ b/lib/common/bottom_app_navigation.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:weather_app/common/controller/pages_index_controller.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/common/widgets/card.dart'; import 'package:weather_app/features/filters_screen/presentation/filters_screen.dart'; import 'package:weather_app/features/home_screen/controller/is_card_visible.dart'; @@ -24,6 +25,8 @@ class _BottomAppNavigationBarState Widget build(BuildContext context) { bool isCardVisible = ref.watch(isCardVisibleProvider); int state = ref.watch(pagesIndexProvider); + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; return Scaffold( extendBody: true, @@ -33,11 +36,11 @@ class _BottomAppNavigationBarState // 🔹 Ortada + buton floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: DecoratedBox( - decoration: const BoxDecoration( + decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black26, + color: context.colors.onSurface, blurRadius: 10, offset: Offset(0, 4), ), @@ -49,10 +52,10 @@ class _BottomAppNavigationBarState onPressed: () { ref .read(isCardVisibleProvider.notifier) - .switchVisibility(!isCardVisible); + .switchVisibility(!isCardVisible); if (!isCardVisible) { - showModalBottomSheet( - barrierColor: Colors.black.withValues(alpha: 0.2), + showModalBottomSheet( + barrierColor: context.colors.onSurface.withValues(alpha: 0.2), context: context, isScrollControlled: true, // tam ekran genişleyebilir backgroundColor: Colors.transparent, // köşeler yumuşak görünür @@ -68,7 +71,7 @@ class _BottomAppNavigationBarState }, child: Icon( isCardVisible ? Icons.close : Icons.add, - color: Colors.white, + color: context.colors.surface, ), ), ), @@ -82,7 +85,7 @@ class _BottomAppNavigationBarState child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: BottomAppBar( - color: Colors.white.withValues(alpha: 0.1), + color: context.colors.surface.withValues(alpha: 0.1), shape: const CircularNotchedRectangle(), notchMargin: 8, child: Row( @@ -92,14 +95,20 @@ class _BottomAppNavigationBarState icon: Icons.person_rounded, label: 'home'.tr(), isActive: state == 0, - onTap: () => ref.read(pagesIndexProvider.notifier).setIndex(0), + onTap: () => + ref.read(pagesIndexProvider.notifier).setIndex(0), + colorScheme: colorScheme, + textTheme: textTheme, ), const SizedBox(width: 60), // orta + buton boşluğu _navButton( icon: Icons.filter_alt_rounded, label: 'filter'.tr(), isActive: state == 1, - onTap: () => ref.read(pagesIndexProvider.notifier).setIndex(1), + onTap: () => + ref.read(pagesIndexProvider.notifier).setIndex(1), + colorScheme: colorScheme, + textTheme: textTheme, ), ], ), @@ -114,6 +123,8 @@ class _BottomAppNavigationBarState required String label, required bool isActive, required VoidCallback onTap, + required TextTheme textTheme, + required ColorScheme colorScheme, }) { return GestureDetector( onTap: onTap, @@ -125,7 +136,7 @@ class _BottomAppNavigationBarState decoration: BoxDecoration( color: isActive - ? const Color(0xFF7F5AF0).withValues(alpha: 0.15) + ? colorScheme.primary.withValues(alpha: 0.15) : Colors.transparent, borderRadius: BorderRadius.circular(12), ), @@ -134,17 +145,14 @@ class _BottomAppNavigationBarState size: 26, color: isActive ? const Color(0xFF7F5AF0) - : Colors.white.withValues(alpha: 0.7), + : context.colors.surface.withValues(alpha: 0.7), ), ), const SizedBox(height: 3), Text( label, - style: TextStyle( - color: isActive - ? const Color(0xFF7F5AF0) - : Colors.white.withValues(alpha: 0.6), - fontSize: 12, + style: textTheme.bodyMedium?.copyWith( + color: isActive ? colorScheme.primary : colorScheme.surface, ), ), ], diff --git a/lib/common/controller/pages_index_controller.dart b/lib/common/controller/pages_index_controller.dart index 28a247b..935e9b0 100644 --- a/lib/common/controller/pages_index_controller.dart +++ b/lib/common/controller/pages_index_controller.dart @@ -9,6 +9,7 @@ class PagesIndex extends _$PagesIndex { return 0; } + // ignore: use_setters_to_change_properties void setIndex(int newIndex) { state = newIndex; } diff --git a/lib/common/controller/select_day_weather.dart b/lib/common/controller/select_day_weather.dart new file mode 100644 index 0000000..9c21b0c --- /dev/null +++ b/lib/common/controller/select_day_weather.dart @@ -0,0 +1,24 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weather_app/features/home_screen/domain/weather_model.dart'; +part 'select_day_weather.g.dart'; +@Riverpod(keepAlive: true) +class SelectDayWeather extends _$SelectDayWeather { + @override + WeatherModel build() { + return WeatherModel( + date: '', + day: '', + description: '', + degree: '', + min: '', + max: '', + night: '', + humidity: '', + ); + } + + // ignore: use_setters_to_change_properties + void switcWeatherModel(WeatherModel weatherModel) { + state = weatherModel; + } +} diff --git a/lib/common/controller/select_day_weather.g.dart b/lib/common/controller/select_day_weather.g.dart new file mode 100644 index 0000000..840dd54 --- /dev/null +++ b/lib/common/controller/select_day_weather.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'select_day_weather.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$selectDayWeatherHash() => r'563ee799a3c227f083a46cab81a6e7623abaf55a'; + +/// See also [SelectDayWeather]. +@ProviderFor(SelectDayWeather) +final selectDayWeatherProvider = + NotifierProvider.internal( + SelectDayWeather.new, + name: r'selectDayWeatherProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$selectDayWeatherHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$SelectDayWeather = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/common/theme_extension b/lib/common/theme_extension new file mode 100644 index 0000000..bdc50c9 --- /dev/null +++ b/lib/common/theme_extension @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +extension ThemeExtension on BuildContext { + ColorScheme get colors => Theme.of(this).colorScheme; + TextTheme get texts => Theme.of(this).textTheme; +} diff --git a/lib/common/widgets/card.dart b/lib/common/widgets/card.dart index c45a39c..595a4bf 100644 --- a/lib/common/widgets/card.dart +++ b/lib/common/widgets/card.dart @@ -3,12 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hexcolor/hexcolor.dart'; import 'package:lottie/lottie.dart'; +import 'package:weather_app/common/controller/select_day_weather.dart'; +import 'package:weather_app/common/theme_extension'; +import 'package:weather_app/common/widgets/detail_card.dart'; import 'package:weather_app/features/home_screen/controller/location_controller.dart'; import 'package:weather_app/features/home_screen/data/weather_data_repository.dart'; import 'package:weather_app/features/home_screen/domain/location_model.dart'; import 'package:weather_app/features/home_screen/domain/weather_model.dart'; import 'package:weather_app/utils/normalize_city_name.dart'; -import 'package:weather_app/utils/to_string_as_fixed.dart'; class CardWidget extends ConsumerStatefulWidget { const CardWidget({super.key}); @@ -18,10 +20,17 @@ class CardWidget extends ConsumerStatefulWidget { } class _CardWidgetState extends ConsumerState { - Color getColor(int day) { - if (DateTime.now().day == day) { + Color getColor(WeatherModel weatherModel, WeatherModel selectWeatherModel) { + if (selectWeatherModel.date.isNotEmpty) { + if (selectWeatherModel.date.substring(3, 5) == + weatherModel.date.substring(3, 5)) { + return HexColor('#48319D'); + } + } else if (DateTime.now().day.toString() == + weatherModel.date.substring(3, 5)) { return HexColor('#48319D'); } + return HexColor('#48319D').withValues(alpha: 0.4); } @@ -29,6 +38,9 @@ class _CardWidgetState extends ConsumerState { Widget build(BuildContext context) { LocationModel location = ref.watch(locationControllerProvider); String city = normalizeCityName(location.cityName); + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; + WeatherModel model = ref.watch(selectDayWeatherProvider); AsyncValue> weatherAsyncValue = ref.watch( weatherDataRepositoryProvider(city), @@ -36,10 +48,10 @@ class _CardWidgetState extends ConsumerState { return weatherAsyncValue.when( data: (List data) { if (data.isEmpty) { - return Center( + return Center( child: Text( 'weather.no_data'.tr(), - style: TextStyle(color: Colors.white), + style: TextStyle(color: colorScheme.surface), ), ); } @@ -61,11 +73,11 @@ class _CardWidgetState extends ConsumerState { ), borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - boxShadow: const [ + boxShadow: [ BoxShadow( blurRadius: 12, - color: Colors.black26, - offset: Offset(0, -4), + color: context.colors.onSurface.withValues(alpha: 0.2), + offset: const Offset(0, -4), ), ], ), @@ -96,273 +108,61 @@ class _CardWidgetState extends ConsumerState { String degree = double.parse( weather.degree, ).toStringAsFixed(0); - return Container( - width: 54, - margin: const EdgeInsets.only(right: 12), - alignment: Alignment.center, - decoration: BoxDecoration( - color: getColor( - int.parse(weather.date.substring(3, 5)), - ), - borderRadius: BorderRadius.circular(32), - border: Border.all(color: Colors.white24), - ), - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - weather.day.substring(0, 3), - style: const TextStyle( - color: Colors.white70, - fontSize: 14, - ), - ), - const SizedBox(height: 8), - Lottie.asset( - 'assets/animated/${weather.status.toString().toLowerCase()}.json', - width: 50, - height: 50, - repeat: true, - ), - const SizedBox(height: 8), - Text( - '$degree°', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), + return GestureDetector( + onTap: () { + ref + .read(selectDayWeatherProvider.notifier) + .switcWeatherModel(weather); + }, + child: Container( + width: 54, + margin: const EdgeInsets.only(right: 12), + alignment: Alignment.center, + decoration: BoxDecoration( + color: getColor(weather, model), + borderRadius: BorderRadius.circular(32), + border: Border.all( + color: context.colors.surface.withValues( + alpha: 0.4, ), - ], - ), - ), - ); - }, - ), - ), - const SizedBox(height: 16), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - gradient: LinearGradient( - colors: [ - HexColor('#48319D').withValues(alpha: 0.85), - HexColor('#2E335A').withValues(alpha: 0.85), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 15, - offset: const Offset(0, 6), - ), - ], - border: Border.all( - color: Colors.white.withValues(alpha: 0.1), - width: 1.2, - ), - ), - padding: const EdgeInsets.all(20), - child: Column( - children: [ - Text( - '${weatherToday.day} • ${weatherToday.date}', - style: const TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - ), - ), - - const SizedBox(height: 14), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset( - 'assets/animated/${weatherToday.status.toString().toLowerCase()}.json', - width: 100, - height: 100, - repeat: true, - ), - const SizedBox(width: 10), - Text( - weatherToday.description[0].toUpperCase() + - weatherToday.description.substring(1), - style: const TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.w600, ), ), - ], - ), - - const SizedBox(height: 20), - - // 🔹 Ana sıcaklık + min/max göstergesi - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ShaderMask( - shaderCallback: (Rect bounds) { - return const LinearGradient( - colors: [ - Colors.white, - Colors.lightBlueAccent, - ], - ).createShader(bounds); - }, - child: Text( - '${double.parse(weatherToday.degree).toStringAsFixed(1)}°', - style: const TextStyle( - fontSize: 64, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'weather_arrow_max'.tr( - namedArgs: {'max': toStringAsFixed(weatherToday.max, 0)} - ), - style: const TextStyle( - color: Colors.white70, - fontSize: 16, - ), - ), - Text( - 'weather_arrow_min'.tr( - namedArgs: {'min': toStringAsFixed(weatherToday.min, 0)} - ), - style: const TextStyle( - color: Colors.white70, - fontSize: 16, - ), - ), - ], - ), - ], - ), - - const SizedBox(height: 16), - - // 🔹 Ek bilgiler (gece sıcaklığı, nem) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white24), - ), - child: Row( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.nights_stay, - color: Colors.white70, - size: 18, - ), - const SizedBox(width: 4), Text( - 'weather_night_temp'.tr( - namedArgs: { - 'night': toStringAsFixed( - weatherToday.night, - 1, - ), - }, - ), - style: const TextStyle( - color: Colors.white70, + weather.day.substring(0, 3), + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.surface, ), ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white24), - ), - child: Row( - children: [ - const Icon( - Icons.water_drop, - color: Colors.white70, - size: 18, + const SizedBox(height: 8), + Lottie.asset( + 'assets/animated/${weather.status.toString().toLowerCase()}.json', + width: 50, + height: 50, + repeat: true, ), - const SizedBox(width: 4), + const SizedBox(height: 8), Text( - 'weather_humidity'.tr( - namedArgs: { - 'humidity': weatherToday.humidity, - }, - ), - style: const TextStyle( - color: Colors.white70, + '$degree°', + style: textTheme.titleMedium?.copyWith( + color: colorScheme.surface, ), ), ], ), ), - ], - ), - - const SizedBox(height: 16), - - // 🔹 Alt açıklama metni (dinamik, orta hizalı) - Text( - 'weather_message'.tr( - namedArgs: { - 'description': weatherToday.description, - 'humidity': weatherToday.humidity, - 'degree': weatherToday.degree, - }, - ), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white70, - fontSize: 15, - height: 1.5, ), - ), - const SizedBox(height: 8), - Container( - height: 2, - width: 100, - margin: const EdgeInsets.only(top: 10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Colors.white.withValues(alpha: 0.8), - Colors.blueAccent.withValues(alpha: 0.4), - Colors.transparent, - ], - ), - borderRadius: BorderRadius.circular(2), - ), - ), - ], + ); + }, ), ), + const SizedBox(height: 16), + DetailCard( + weatherModel: model.date.isEmpty ? weatherToday : model, + ), ], ), ); diff --git a/lib/common/widgets/detail_card.dart b/lib/common/widgets/detail_card.dart new file mode 100644 index 0000000..24fce35 --- /dev/null +++ b/lib/common/widgets/detail_card.dart @@ -0,0 +1,250 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hexcolor/hexcolor.dart'; +import 'package:lottie/lottie.dart'; +import 'package:weather_app/common/theme_extension'; +import 'package:weather_app/features/home_screen/domain/weather_model.dart'; +import 'package:weather_app/utils/to_string_as_fixed.dart'; + +class DetailCard extends StatelessWidget { + const DetailCard({super.key,required this.weatherModel}); + final WeatherModel weatherModel; + + @override + Widget build(BuildContext context) { + TextTheme textTheme=Theme.of(context).textTheme; + ColorScheme colorScheme=Theme.of(context).colorScheme; + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + colors: [ + HexColor('#48319D').withValues(alpha: 0.85), + HexColor('#2E335A').withValues(alpha: 0.85), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: context.colors.onSurface.withValues( + alpha: 0.3, + ), + blurRadius: 15, + offset: const Offset(0, 6), + ), + ], + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.1), + width: 1.2, + ), + ), + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Text( + '${weatherModel.day} • ${weatherModel.date}', + style: textTheme.headlineSmall?.copyWith( + color: colorScheme.surface, + letterSpacing: 1.2, + ), + ), + + const SizedBox(height: 14), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Lottie.asset( + 'assets/animated/${weatherModel.status.toString().toLowerCase()}.json', + width: 100, + height: 100, + repeat: true, + ), + const SizedBox(width: 10), + Text( + weatherModel.description[0].toUpperCase() + + weatherModel.description.substring(1), + style: textTheme.headlineSmall?.copyWith( + color: colorScheme.surface, + ), + ), + ], + ), + + const SizedBox(height: 20), + + // 🔹 Ana sıcaklık + min/max göstergesi + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient( + colors: [ + context.colors.surface, + Colors.lightBlueAccent, + ], + ).createShader(bounds); + }, + child: Text( + '${double.parse(weatherModel.degree).toStringAsFixed(1)}°', + style: textTheme.displayLarge?.copyWith( + color: colorScheme.surface, + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'weather_arrow_max'.tr( + namedArgs: { + 'max': toStringAsFixed( + weatherModel.max, + 0, + ), + }, + ), + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.surface, + ), + ), + Text( + 'weather_arrow_min'.tr( + namedArgs: { + 'min': toStringAsFixed( + weatherModel .min, + 0, + ), + }, + ), + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.surface, + ), + ), + ], + ), + ], + ), + + const SizedBox(height: 16), + + // 🔹 Ek bilgiler (gece sıcaklığı, nem) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: context.colors.surface.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: context.colors.surface, + ), + ), + child: Row( + children: [ + Icon( + Icons.nights_stay, + color: context.colors.surface, + size: 18, + ), + const SizedBox(width: 4), + Text( + 'weather_night_temp'.tr( + namedArgs: { + 'night': toStringAsFixed( + weatherModel.night, + 1, + ), + }, + ), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.surface, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: context.colors.surface.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: context.colors.surface, + ), + ), + child: Row( + children: [ + Icon( + Icons.water_drop, + color: context.colors.surface, + size: 18, + ), + const SizedBox(width: 4), + Text( + 'weather_humidity'.tr( + namedArgs: { + 'humidity': weatherModel.humidity, + }, + ), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.surface, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 16), + + // 🔹 Alt açıklama metni (dinamik, orta hizalı) + Text( + 'weather_message'.tr( + namedArgs: { + 'description': weatherModel.description, + 'humidity': weatherModel.humidity, + 'degree': weatherModel.degree, + }, + ), + textAlign: TextAlign.center, + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.surface.withValues(alpha: 0.8), + height: 1.5, + ), + ), + const SizedBox(height: 8), + Container( + height: 2, + width: 100, + margin: const EdgeInsets.only(top: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + context.colors.surface.withValues(alpha: 0.8), + Colors.blueAccent.withValues(alpha: 0.4), + Colors.transparent, + ], + ), + borderRadius: BorderRadius.circular(2), + ), + ), + ], + ), + ); + } +} diff --git a/lib/core/theme/controller/theme_mode_controller.dart b/lib/core/theme/controller/theme_mode_controller.dart new file mode 100644 index 0000000..972e31b --- /dev/null +++ b/lib/core/theme/controller/theme_mode_controller.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'theme_mode_controller.g.dart'; + +@riverpod +class ThemeModeController extends _$ThemeModeController { + @override + ThemeMode build() { + // Varsayılan tema: açık + return ThemeMode.light; + } + + /// Tema değiştirici + void switchTheme() { + if (state == ThemeMode.light) { + state = ThemeMode.dark; + } else { + state = ThemeMode.light; + } + } +} diff --git a/lib/core/theme/controller/theme_mode_controller.g.dart b/lib/core/theme/controller/theme_mode_controller.g.dart new file mode 100644 index 0000000..f22ccf8 --- /dev/null +++ b/lib/core/theme/controller/theme_mode_controller.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'theme_mode_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$themeModeControllerHash() => + r'a6f42f77eb059df8a24443cda53e0315a621ec21'; + +/// See also [ThemeModeController]. +@ProviderFor(ThemeModeController) +final themeModeControllerProvider = + AutoDisposeNotifierProvider.internal( + ThemeModeController.new, + name: r'themeModeControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$themeModeControllerHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$ThemeModeController = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/core/theme/theme_palette.dart b/lib/core/theme/theme_palette.dart new file mode 100644 index 0000000..89adc8b --- /dev/null +++ b/lib/core/theme/theme_palette.dart @@ -0,0 +1,26 @@ +// Tema renkleri için FlexColorScheme kullanımı +// Light ve Dark mode için ayrı renk setleri tanımlanıyor. + +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter/material.dart'; +import 'package:hexcolor/hexcolor.dart'; + +/// Light Mode renkleri +final FlexSchemeColor myLightColors = FlexSchemeColor( + primary: Color(0xFF7F5AF0), // Ana renk (mavi tonları) + primaryContainer: const Color(0xFF90CAF9), // Açık mavi arka plan + secondary: const Color(0xFF00ACC1), // İkincil renk (turkuaz) + secondaryContainer: const Color(0xFFB2EBF2), // Açık turkuaz arka plan + tertiary: Colors.green.shade500, // Ek renk (yeşil tonları) + tertiaryContainer: const Color(0xFFB2EBF2), // Ek renk arka plan +); + +/// Dark Mode renkleri +final FlexSchemeColor myDarkColors = FlexSchemeColor( + primary: HexColor('#91C8E4'), // Ana renk (koyu mavi tonları) + primaryContainer: const Color(0xFF5472D3), // Koyu mavi arka plan + secondary: const Color(0xFF00838F), // İkincil renk (koyu turkuaz) + secondaryContainer: const Color(0xFF4DD0E1), // Açık turkuaz + tertiary: const Color(0xFF006064), // Ek renk (koyu petrol mavisi) + tertiaryContainer: const Color(0xFF4DD0E1), // Ek renk arka plan +); diff --git a/lib/features/filters_screen/controller/city_controller.dart b/lib/features/filters_screen/controller/city_controller.dart index b240a53..a1d53b9 100644 --- a/lib/features/filters_screen/controller/city_controller.dart +++ b/lib/features/filters_screen/controller/city_controller.dart @@ -8,6 +8,7 @@ class CityController extends _$CityController { return ''; } + // ignore: use_setters_to_change_properties void updateCity(String newCity) { state = newCity; } diff --git a/lib/features/filters_screen/presentation/filters_screen.dart b/lib/features/filters_screen/presentation/filters_screen.dart index ad6a323..bb6e7aa 100644 --- a/lib/features/filters_screen/presentation/filters_screen.dart +++ b/lib/features/filters_screen/presentation/filters_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/features/filters_screen/presentation/widgets/error_screen.dart'; import 'package:weather_app/features/filters_screen/presentation/widgets/header_city_row.dart'; import 'package:weather_app/features/filters_screen/presentation/widgets/skeleton_list.dart'; @@ -23,6 +24,8 @@ class FiltersScreen extends ConsumerStatefulWidget { class _FiltersScreenState extends ConsumerState { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; LocationModel location = ref.watch(locationControllerProvider); String city = normalizeCityName(location.cityName); @@ -31,19 +34,17 @@ class _FiltersScreenState extends ConsumerState { ); return Scaffold( - backgroundColor: Colors.black, + backgroundColor: context.colors.onSurface, appBar: AppBar( elevation: 0, toolbarHeight: 80, backgroundColor: Colors.transparent, centerTitle: true, - title: const Text( + title: Text( 'Hava Durumu', - style: TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.w700, - letterSpacing: .4, + style: textTheme.headlineMedium?.copyWith( + color: colorScheme.surface, + letterSpacing: 0.4, ), ), ), @@ -101,8 +102,9 @@ class _FiltersScreenState extends ConsumerState { message: 'error_data_load'.tr( namedArgs: {'error': error.toString()}, ), - onRetry: () => - ref.refresh(weatherDataRepositoryProvider(location.cityName)), + onRetry: () => ref.invalidate( + weatherDataRepositoryProvider(location.cityName), + ), ); }, loading: () => const SkeletonList(), diff --git a/lib/features/filters_screen/presentation/widgets/build_child.dart b/lib/features/filters_screen/presentation/widgets/build_child.dart index e5b428c..f378841 100644 --- a/lib/features/filters_screen/presentation/widgets/build_child.dart +++ b/lib/features/filters_screen/presentation/widgets/build_child.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/features/filters_screen/controller/city_controller.dart'; import 'package:weather_app/features/filters_screen/domain/city_model.dart'; import 'package:weather_app/features/home_screen/controller/location_controller.dart'; @@ -12,6 +13,8 @@ class BuildChild extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; List cities2 = cities.map((CityModel city) => city.name).toList(); return Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -27,10 +30,9 @@ class BuildChild extends ConsumerWidget { children: [ Text( 'city_select_title'.tr(), - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, + style: textTheme.titleLarge?.copyWith( color: Colors.deepPurple, + fontWeight: FontWeight.w500, ), ), IconButton( @@ -51,7 +53,9 @@ class BuildChild extends ConsumerWidget { // Açıklama Metni Text( 'city_select_hint'.tr(), - style: TextStyle(fontSize: 16, color: Colors.black87), + style: textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.85), + ), textAlign: TextAlign.center, ), const SizedBox(height: 20), @@ -86,9 +90,7 @@ class BuildChild extends ConsumerWidget { items: cities2.map>((String value) { return DropdownMenuItem( value: value, - child: Text( - value, - ), + child: Text(value), ); }).toList(), onChanged: (String? newValue) { @@ -96,13 +98,12 @@ class BuildChild extends ConsumerWidget { }, borderRadius: BorderRadius.circular(16), icon: const Icon(Icons.arrow_drop_down, color: Colors.deepPurple), - dropdownColor: Colors.white, + dropdownColor: context.colors.surface, elevation: 15, - style: const TextStyle( - fontSize: 17, - color: Colors.black, - letterSpacing: 0.5, - ), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface, + letterSpacing: 0.5, + ), ), const SizedBox(height: 30), @@ -125,10 +126,7 @@ class BuildChild extends ConsumerWidget { ), child: Text( 'city_cancel'.tr(), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: textTheme.titleMedium, ), ), ), @@ -154,7 +152,7 @@ class BuildChild extends ConsumerWidget { }, style: ElevatedButton.styleFrom( backgroundColor: Colors.deepPurple, // Arka plan rengi - foregroundColor: Colors.white, // Yazı rengi + foregroundColor: context.colors.surface, // Yazı rengi shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -163,9 +161,8 @@ class BuildChild extends ConsumerWidget { ), child: Text( 'city_select_button'.tr(), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + style: textTheme.titleMedium?.copyWith( + color: colorScheme.surface, ), ), ), diff --git a/lib/features/filters_screen/presentation/widgets/city_choose_alert.dart b/lib/features/filters_screen/presentation/widgets/city_choose_alert.dart index eac9b53..396ea78 100644 --- a/lib/features/filters_screen/presentation/widgets/city_choose_alert.dart +++ b/lib/features/filters_screen/presentation/widgets/city_choose_alert.dart @@ -16,6 +16,8 @@ class CityChooseAlert extends ConsumerStatefulWidget { class _CityChooseAlertState extends ConsumerState { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; AsyncValue> cityAsyncValue = ref.watch(getCityDataProvider); return AlertDialog( // AlertDialog yerine Dialog kullanmak, içeriği daha serbest kontrol etmemizi sağlar @@ -27,7 +29,9 @@ class _CityChooseAlertState extends ConsumerState { if (cities.isEmpty) { return Text( 'city_not_found'.tr(), - style: TextStyle(color: Colors.red), + style: textTheme.titleMedium?.copyWith( + color: colorScheme.primary, + ), ); } @@ -39,7 +43,7 @@ class _CityChooseAlertState extends ConsumerState { 'error_generic'.tr( namedArgs: {'err': err.toString()}, ), - style: const TextStyle(color: Colors.red), + style: TextStyle(color: colorScheme.primary), ), ), ), diff --git a/lib/features/filters_screen/presentation/widgets/city_picker_card.dart b/lib/features/filters_screen/presentation/widgets/city_picker_card.dart deleted file mode 100644 index f3c34cf..0000000 --- a/lib/features/filters_screen/presentation/widgets/city_picker_card.dart +++ /dev/null @@ -1,205 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:weather_app/features/filters_screen/controller/city_controller.dart'; - -class CityPickerCard extends ConsumerWidget { - const CityPickerCard({super.key, required this.cities2}); - final List cities2; - - @override - Widget build(BuildContext context, WidgetRef ref) { - // mevcut seçili şehir (örnek: cityControllerProvider’dan okuyoruz) - final selectedCity = ref.watch(cityControllerProvider); - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Material( - elevation: 6, - borderRadius: BorderRadius.circular(20), - color: Colors.white.withOpacity(0.06), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - // hafif gradient arkaplan - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0x1A7E57C2), Color(0x1A5E35B1)], - ), - border: Border.all(color: Colors.white.withOpacity(0.10)), - ), - padding: const EdgeInsets.fromLTRB(16, 16, 16, 18), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Başlık satırı - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: const Color(0xFF5B3FC4).withOpacity(0.15), - borderRadius: BorderRadius.circular(14), - ), - child: const Icon( - Icons.location_city_rounded, - color: Color(0xFF5B3FC4), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'city_select_title'.tr(), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w700, - color: Colors.white, - ), - ), - const SizedBox(height: 4), - Text( - 'city_select_hint'.tr(), - style: TextStyle( - fontSize: 13, - color: Colors.white.withOpacity(0.85), - ), - ), - ], - ), - ), - ], - ), - - const SizedBox(height: 14), - - // Dropdown alanı - DropdownButtonFormField( - value: cities2.contains(selectedCity) ? selectedCity : null, - icon: const Icon( - Icons.keyboard_arrow_down_rounded, - color: Color(0xFF5B3FC4), - ), - dropdownColor: const Color(0xFF141321), - elevation: 8, - style: const TextStyle(fontSize: 15, color: Colors.white), - - decoration: InputDecoration( - labelText: 'city_field_label'.tr(), - prefixIcon: const Icon( - Icons.location_on_rounded, - color: Color(0xFF5B3FC4), - ), - filled: true, - fillColor: Colors.white.withOpacity(0.07), - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 14, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide( - color: Colors.white.withOpacity(0.18), - width: 1, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: const BorderSide( - color: Color(0xFF5B3FC4), - width: 1.8, - ), - ), - helperText: ' ', // form yüksekliğini dengeler - helperStyle: const TextStyle(height: 0.8), - ), - - menuMaxHeight: 320, - - items: cities2.map((value) { - return DropdownMenuItem( - value: value, - child: Row( - children: [ - const Icon( - Icons.location_on_outlined, - size: 18, - color: Colors.white70, - ), - const SizedBox(width: 8), - Flexible( - child: Text( - value, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 15, - color: Colors.white, - ), - ), - ), - ], - ), - ); - }).toList(), - - onChanged: (val) { - if (val != null) { - ref.read(cityControllerProvider.notifier).updateCity(val); - } - }, - ), - - // Alt satır: seçili şehir rozeti - const SizedBox(height: 8), - if (selectedCity.isNotEmpty) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - decoration: BoxDecoration( - color: const Color(0xFF5B3FC4).withOpacity(0.18), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color(0xFF5B3FC4).withOpacity(0.35), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.check_circle_rounded, - size: 16, - color: Color(0xFFB7A9FF), - ), - const SizedBox(width: 6), - Text( - 'weather_header_city_country'.tr( - namedArgs: { - 'city': selectedCity, - // TR sabit ülke metni JSON içinde: "{city}, Türkiye" - }, - ), - style: const TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/features/filters_screen/presentation/widgets/codition_fill.dart b/lib/features/filters_screen/presentation/widgets/codition_fill.dart index 25ab7ba..bf8dc4d 100644 --- a/lib/features/filters_screen/presentation/widgets/codition_fill.dart +++ b/lib/features/filters_screen/presentation/widgets/codition_fill.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:weather_app/common/theme_extension'; // Hava Durumu koşulunu gösteren küçük container class ConditionPill extends StatelessWidget { @@ -7,22 +8,21 @@ class ConditionPill extends StatelessWidget { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; String t = text.isEmpty ? '—' : text.toUpperCase(); return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.07), + color: context.colors.surface.withValues(alpha: 0.07), borderRadius: BorderRadius.circular(999), - border: Border.all(color: Colors.white.withValues(alpha: 0.12)), + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.12), + ), ), child: Text( t, - style: const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.w600, - letterSpacing: .6, - ), + style: textTheme.titleSmall?.copyWith(color: colorScheme.surface), ), ); } diff --git a/lib/features/filters_screen/presentation/widgets/error_screen.dart b/lib/features/filters_screen/presentation/widgets/error_screen.dart index fd99e7b..cb90f0d 100644 --- a/lib/features/filters_screen/presentation/widgets/error_screen.dart +++ b/lib/features/filters_screen/presentation/widgets/error_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hexcolor/hexcolor.dart'; +import 'package:weather_app/common/theme_extension'; // Error ekranı bileşeni class ErrorState extends StatelessWidget { @@ -10,6 +11,8 @@ class ErrorState extends StatelessWidget { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( @@ -24,22 +27,30 @@ class ErrorState extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error_outline, color: Colors.white70, size: 48), + Icon( + Icons.error_outline, + color: context.colors.surface, + size: 48, + ), const SizedBox(height: 12), Text( message, - style: const TextStyle(color: Colors.white, fontSize: 14), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.surface, + ), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton( onPressed: onRetry, style: ElevatedButton.styleFrom( - backgroundColor: Colors.white.withValues(alpha: 0.12), - foregroundColor: Colors.white, + backgroundColor: context.colors.surface.withValues( + alpha: 0.12, + ), + foregroundColor: context.colors.surface, elevation: 0, ), - child: Text('ui.retry'.tr()), + child: Text('ui_retry'.tr()), ), ], ), diff --git a/lib/features/filters_screen/presentation/widgets/header_city_row.dart b/lib/features/filters_screen/presentation/widgets/header_city_row.dart index 09478d5..316e31e 100644 --- a/lib/features/filters_screen/presentation/widgets/header_city_row.dart +++ b/lib/features/filters_screen/presentation/widgets/header_city_row.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/features/filters_screen/presentation/widgets/city_choose_alert.dart'; // Ekranın üst kısmındaki şehir ve ülke bilgisini gösteren bileşen @@ -11,6 +12,8 @@ class HeaderCityRow extends StatelessWidget { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; return Row( children: [ // metinler @@ -20,18 +23,15 @@ class HeaderCityRow extends StatelessWidget { children: [ Text( '$city, $country', - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w600, + style: textTheme.titleLarge?.copyWith( + color: colorScheme.surface, ), ), const SizedBox(height: 6), Text( 'today_summary'.tr(), - style: TextStyle( - color: Colors.white.withValues(alpha: 0.85), - fontSize: 14, + style: textTheme.titleSmall?.copyWith( + color: colorScheme.surface.withValues(alpha: 0.85), ), ), ], @@ -40,12 +40,12 @@ class HeaderCityRow extends StatelessWidget { Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.white.withValues(alpha: 0.12), - foregroundColor: Colors.white, + backgroundColor: context.colors.surface.withValues(alpha: 0.12), + foregroundColor: context.colors.surface, elevation: 0, ), onPressed: () { - showDialog( + showDialog( context: context, builder: (BuildContext context) => CityChooseAlert(), ); diff --git a/lib/features/filters_screen/presentation/widgets/mini_chip.dart b/lib/features/filters_screen/presentation/widgets/mini_chip.dart index 19fff63..4e8bc44 100644 --- a/lib/features/filters_screen/presentation/widgets/mini_chip.dart +++ b/lib/features/filters_screen/presentation/widgets/mini_chip.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:weather_app/common/theme_extension'; //ekranın alt kısmındaki max ve min değerlerini gösteren container class MiniChip extends StatelessWidget { @@ -14,28 +15,34 @@ class MiniChip extends StatelessWidget { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.06), + color: context.colors.surface.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withValues(alpha: 0.12)), + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.12), + ), ), child: Row( children: [ - Icon(icon, size: 16, color: Colors.white.withValues(alpha: 0.9)), + Icon( + icon, + size: 16, + color: context.colors.surface.withValues(alpha: 0.9), + ), const SizedBox(width: 6), Text( '$label: ', - style: TextStyle(color: Colors.white.withValues(alpha: 0.85), fontSize: 12), + style: textTheme.titleSmall?.copyWith( + color: colorScheme.surface.withValues(alpha: 0.8), + ), ), Text( value, - style: const TextStyle( - color: Colors.white, - fontSize: 12.5, - fontWeight: FontWeight.w700, - ), + style: textTheme.titleSmall?.copyWith(color: colorScheme.surface), ), ], ), diff --git a/lib/features/filters_screen/presentation/widgets/skeleton_card.dart b/lib/features/filters_screen/presentation/widgets/skeleton_card.dart index d3e63ea..e073b43 100644 --- a/lib/features/filters_screen/presentation/widgets/skeleton_card.dart +++ b/lib/features/filters_screen/presentation/widgets/skeleton_card.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:weather_app/common/theme_extension'; // Ekran yüklenirken gösterilen iskelet kart bileşeni class SkeletonCard extends StatelessWidget { @@ -11,18 +12,20 @@ class SkeletonCard extends StatelessWidget { child: Container( height: 190, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.06), + color: context.colors.surface.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(28), - border: Border.all(color: Colors.white.withValues(alpha: 0.12)), + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.12), + ), ), child: Row( children: [ const SizedBox(width: 18), - _shimmerBox(width: 80, height: 40), + _shimmerBox(width: 80, height: 40, context: context), const SizedBox(width: 12), - _shimmerBox(width: 90, height: 22), + _shimmerBox(width: 90, height: 22, context: context), const Spacer(), - _shimmerBox(width: 70, height: 70), + _shimmerBox(width: 70, height: 70, context: context), const SizedBox(width: 18), ], ), @@ -30,14 +33,18 @@ class SkeletonCard extends StatelessWidget { ); } - Widget _shimmerBox({required double width, required double height}) { + Widget _shimmerBox({ + required double width, + required double height, + required BuildContext context, + }) { return AnimatedContainer( duration: const Duration(milliseconds: 1000), curve: Curves.easeInOut, width: width, height: height, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.08), + color: context.colors.surface.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(14), ), ); diff --git a/lib/features/filters_screen/presentation/widgets/weathar_card.dart b/lib/features/filters_screen/presentation/widgets/weathar_card.dart index 6772c7b..34c9a0e 100644 --- a/lib/features/filters_screen/presentation/widgets/weathar_card.dart +++ b/lib/features/filters_screen/presentation/widgets/weathar_card.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/features/filters_screen/presentation/widgets/codition_fill.dart'; import 'package:weather_app/features/filters_screen/presentation/widgets/mini_chip.dart'; import 'package:weather_app/features/filters_screen/presentation/widgets/weather_icon.dart'; @@ -20,6 +21,8 @@ class WeatherCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { LocationModel location = ref.watch(locationControllerProvider); + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; return ClipRRect( borderRadius: BorderRadius.circular(28), @@ -30,17 +33,19 @@ class WeatherCard extends ConsumerWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Colors.white.withValues(alpha: 0.08), - Colors.white.withValues(alpha: 0.04), + context.colors.surface.withValues(alpha: 0.08), + context.colors.surface.withValues(alpha: 0.04), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), - border: Border.all(color: Colors.white.withValues(alpha: 0.12)), + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.12), + ), borderRadius: BorderRadius.circular(28), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: 0.25), + color: context.colors.onSurface.withValues(alpha: 0.25), blurRadius: 18, offset: const Offset(0, 10), ), @@ -52,22 +57,25 @@ class WeatherCard extends ConsumerWidget { filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), child: Container( padding: const EdgeInsets.all(18), - height: 190, + height: 230, width: double.infinity, alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // dereceler + ikon + Text( + weather.day, + style: textTheme.bodyLarge?.copyWith( + color: context.colors.surface, + ), + ), Row( children: [ Text( '${degree.toStringAsFixed(0)}°', - style: const TextStyle( - color: Colors.white, - fontSize: 40, - fontWeight: FontWeight.w800, - height: 1, + style: textTheme.headlineLarge?.copyWith( + color: colorScheme.surface, ), ), const SizedBox(width: 12), @@ -82,10 +90,8 @@ class WeatherCard extends ConsumerWidget { // şehir/ülke Text( '${location.cityName}, ${location.countryName}', - style: const TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.w600, + style: textTheme.titleMedium?.copyWith( + color: colorScheme.surface, ), ), diff --git a/lib/features/filters_screen/presentation/widgets/weather_icon.dart b/lib/features/filters_screen/presentation/widgets/weather_icon.dart index 13e94b7..353fc65 100644 --- a/lib/features/filters_screen/presentation/widgets/weather_icon.dart +++ b/lib/features/filters_screen/presentation/widgets/weather_icon.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; +import 'package:weather_app/common/theme_extension'; /// Hava durumu simgesini gösteren bileşen class WeatherIcon extends StatelessWidget { @@ -12,13 +13,15 @@ class WeatherIcon extends StatelessWidget { height: 70, width: 70, decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.06), + color: context.colors.surface.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.white.withValues(alpha: 0.12)), + border: Border.all( + color: context.colors.surface.withValues(alpha: 0.12), + ), ), clipBehavior: Clip.antiAlias, child: (url.isEmpty) - ? const Icon(Icons.cloud, color: Colors.white, size: 40) + ? Icon(Icons.cloud, color: context.colors.surface, size: 40) : Lottie.asset( 'assets/animated/${url.toLowerCase()}.json', repeat: true, diff --git a/lib/features/home_screen/controller/is_card_visible.dart b/lib/features/home_screen/controller/is_card_visible.dart index 50052e9..046307c 100644 --- a/lib/features/home_screen/controller/is_card_visible.dart +++ b/lib/features/home_screen/controller/is_card_visible.dart @@ -8,6 +8,7 @@ class IsCardVisible extends _$IsCardVisible { return false; } + // ignore: avoid_positional_boolean_parameters, use_setters_to_change_properties void switchVisibility(bool isVisible) { state = isVisible; } diff --git a/lib/features/home_screen/controller/location_controller.dart b/lib/features/home_screen/controller/location_controller.dart index 55683b8..b07bdd6 100644 --- a/lib/features/home_screen/controller/location_controller.dart +++ b/lib/features/home_screen/controller/location_controller.dart @@ -13,6 +13,7 @@ class LocationController extends _$LocationController { longitude: 0, ); } + // ignore: use_setters_to_change_properties void updateLocation(LocationModel newLocation) { state = newLocation; } diff --git a/lib/features/home_screen/data/weather_data_repository.dart b/lib/features/home_screen/data/weather_data_repository.dart index c880889..d1ac6bf 100644 --- a/lib/features/home_screen/data/weather_data_repository.dart +++ b/lib/features/home_screen/data/weather_data_repository.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weather_app/features/home_screen/domain/weather_model.dart'; @@ -15,7 +14,7 @@ Future> weatherDataRepository(Ref ref, String city) async { 'https://api.collectapi.com/weather/getWeather?lang=tr&city=$city', options: Options( headers: { - 'authorization': 'apikey ${dotenv.env['API_KEY']}', + 'authorization': 'apikey 31QfpYJrZQMyc1DDy2yP2T:5F5a6rC1re42ZICQOrQo8Y', 'content-type': 'application/json', }, validateStatus: (int? s) => s != null && s < 500, diff --git a/lib/features/home_screen/domain/weather_model.freezed.dart b/lib/features/home_screen/domain/weather_model.freezed.dart index 8e0f5e2..e26ea2d 100644 --- a/lib/features/home_screen/domain/weather_model.freezed.dart +++ b/lib/features/home_screen/domain/weather_model.freezed.dart @@ -28,8 +28,7 @@ mixin _$WeatherModel { String get min => throw _privateConstructorUsedError; String get max => throw _privateConstructorUsedError; String get night => throw _privateConstructorUsedError; - String get humidity => throw _privateConstructorUsedError; // ← düzeltildi - // (İstersen) + String get humidity => throw _privateConstructorUsedError; String? get icon => throw _privateConstructorUsedError; String? get status => throw _privateConstructorUsedError; @@ -268,8 +267,6 @@ class _$WeatherModelImpl implements _WeatherModel { final String night; @override final String humidity; - // ← düzeltildi - // (İstersen) @override final String? icon; @override @@ -361,8 +358,7 @@ abstract class _WeatherModel implements WeatherModel { @override String get night; @override - String get humidity; // ← düzeltildi - // (İstersen) + String get humidity; @override String? get icon; @override diff --git a/lib/features/home_screen/presentation/home_screen.dart b/lib/features/home_screen/presentation/home_screen.dart index 90db2eb..5b040df 100644 --- a/lib/features/home_screen/presentation/home_screen.dart +++ b/lib/features/home_screen/presentation/home_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:weather_app/common/theme_extension'; import 'package:weather_app/features/home_screen/controller/fetch_on_open.dart'; import 'package:weather_app/features/home_screen/controller/location_controller.dart'; import 'package:weather_app/features/home_screen/data/weather_data_repository.dart'; @@ -18,12 +19,16 @@ class HomeScreen extends ConsumerStatefulWidget { class _HomeScreenState extends ConsumerState { @override Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + TextTheme textTheme = Theme.of(context).textTheme; ref.watch(fetchOnOpenProvider); LocationModel location = ref.watch(locationControllerProvider); if (location.cityName == 'city_selected_fallback'.tr()) { - return const Scaffold( - backgroundColor: Colors.black, - body: Center(child: CircularProgressIndicator(color: Colors.white)), + return Scaffold( + backgroundColor: context.colors.onSurface, + body: Center( + child: CircularProgressIndicator(color: context.colors.surface), + ), ); } String city = normalizeCityName(location.cityName); @@ -33,15 +38,14 @@ class _HomeScreenState extends ConsumerState { ); return Scaffold( - backgroundColor: Colors.black, // Yedek koyu arka plan + backgroundColor: context.colors.onSurface, // Yedek koyu arka plan body: weatherAsyncValue.when( data: (List data) { WeatherModel weather = data[0]; - print(weather.status); double degree = double.parse(weather.degree); return Container( - margin: const EdgeInsets.only(bottom: 60), + margin: const EdgeInsets.only(bottom: 40), decoration: BoxDecoration( image: DecorationImage( image: AssetImage( @@ -55,41 +59,28 @@ class _HomeScreenState extends ConsumerState { color: Colors.black54, child: Center( child: Column( + spacing: 20, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - const SizedBox(height: 50), + const SizedBox(height: 30), Text( 'weather_header_city_country'.tr( namedArgs: {'city': location.cityName}, ), - style: const TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - shadows: [ - Shadow(blurRadius: 6, color: Colors.black54), - ], + style: textTheme.headlineMedium?.copyWith( + color: colorScheme.surface, ), ), Text( '${degree.toStringAsFixed(0)}°', - style: const TextStyle( - color: Colors.white, - fontSize: 72, - fontWeight: FontWeight.w600, - shadows: [ - Shadow(blurRadius: 8, color: Colors.black54), - ], + style: textTheme.displayLarge?.copyWith( + color: colorScheme.surface, ), ), Text( weather.description.toUpperCase(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - shadows: [ - Shadow(blurRadius: 5, color: Colors.black87), - ], + style: textTheme.titleLarge?.copyWith( + color: colorScheme.surface, ), ), Text( @@ -99,12 +90,8 @@ class _HomeScreenState extends ConsumerState { 'min': weather.min, }, ), - style: const TextStyle( - color: Colors.white70, - fontSize: 22, - shadows: [ - Shadow(blurRadius: 4, color: Colors.black54), - ], + style: textTheme.titleLarge?.copyWith( + color: colorScheme.surface, ), ), Image.asset( @@ -122,11 +109,12 @@ class _HomeScreenState extends ConsumerState { 'error_generic'.tr( namedArgs: {'error': error.toString()}, ), - style: const TextStyle(color: Colors.redAccent), + style: textTheme.bodyMedium?.copyWith(color: colorScheme.surface), ), ), - loading: () => - const Center(child: CircularProgressIndicator(color: Colors.white)), + loading: () => Center( + child: CircularProgressIndicator(color: colorScheme.surface), + ), ), ); } diff --git a/lib/main.dart b/lib/main.dart index f65f281..03f9d51 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:weather_app/core/theme/theme_palette.dart'; import 'package:weather_app/route/app_routes.dart'; void main() async { @@ -16,11 +18,11 @@ void main() async { ); } -class MyApp extends StatelessWidget { +class MyApp extends ConsumerWidget { const MyApp({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context,WidgetRef ref) { return MaterialApp.router( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, @@ -28,9 +30,25 @@ class MyApp extends StatelessWidget { routerConfig: appRoutes, title: 'Flutter Demo', debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + theme: FlexThemeData.light( + colors: myLightColors, // Light renk paleti + subThemesData: const FlexSubThemesData( + elevatedButtonRadius: 12, // Buton köşe yuvarlatma + inputDecoratorRadius: 12, // Input köşe yuvarlatma + useMaterial3Typography: true, // Material 3 tipografi + ), ), + + // Dark tema + darkTheme: FlexThemeData.dark( + colors: myDarkColors, // Dark renk paleti + subThemesData: const FlexSubThemesData( + elevatedButtonRadius: 12, + inputDecoratorRadius: 12, + useMaterial3Typography: true, + ), + ), + /* themeMode: ref.watch(themeModeControllerProvider), */ ); } } diff --git a/lib/utils/parse_degre_safe.dart b/lib/utils/parse_degre_safe.dart index 64233bb..e13c68f 100644 --- a/lib/utils/parse_degre_safe.dart +++ b/lib/utils/parse_degre_safe.dart @@ -2,4 +2,4 @@ double parseDegreeSafe(String? s) { if (s == null) return 0; String sanitized = s.replaceAll(',', '.'); return double.tryParse(sanitized) ?? 0; -} \ No newline at end of file +}