├── lib
├── core
│ ├── .gitignore
│ ├── constants.dart
│ ├── usecases
│ │ └── usecase.dart
│ └── dependency_injection.dart
├── features
│ └── app
│ │ ├── domain
│ │ ├── entities
│ │ │ ├── cities_enum.dart
│ │ │ ├── summary_builder.dart
│ │ │ ├── weather_entity.dart
│ │ │ └── forecast_weather_entity.dart
│ │ ├── repository
│ │ │ ├── storage_repository.dart
│ │ │ └── weather_repository.dart
│ │ └── usecases
│ │ │ ├── get_cities.dart
│ │ │ ├── get_colors.dart
│ │ │ ├── delete_city.dart
│ │ │ ├── save_city.dart
│ │ │ ├── get_current_weather.dart
│ │ │ └── get_weather_forecast.dart
│ │ ├── presentation
│ │ ├── bloc
│ │ │ ├── get_current_weather
│ │ │ │ ├── get_current_weather_event.dart
│ │ │ │ ├── get_current_weather_state.dart
│ │ │ │ └── get_current_weather_bloc.dart
│ │ │ ├── get_weather_forecast
│ │ │ │ ├── get_weather_forecast_event.dart
│ │ │ │ ├── get_weather_forecast_state.dart
│ │ │ │ └── get_weather_forecast_bloc.dart
│ │ │ ├── add_city
│ │ │ │ └── cities_changed_cubit.dart
│ │ │ ├── widgets
│ │ │ │ ├── cities_cubit_widget.dart
│ │ │ │ ├── forecast_bloc_widget.dart
│ │ │ │ └── current_weather_bloc_widget.dart
│ │ │ └── blocs_provider_builder.dart
│ │ ├── widgets
│ │ │ ├── summary_text
│ │ │ │ ├── summary_text.dart
│ │ │ │ └── summary.dart
│ │ │ ├── temperature
│ │ │ │ ├── temperature_text.dart
│ │ │ │ ├── temperature_animation.dart
│ │ │ │ └── temperature.dart
│ │ │ ├── condition
│ │ │ │ ├── condition_text.dart
│ │ │ │ └── condition.dart
│ │ │ ├── misc
│ │ │ │ ├── headers
│ │ │ │ │ ├── summary_header
│ │ │ │ │ │ ├── header_text.dart
│ │ │ │ │ │ └── header.dart
│ │ │ │ │ └── forecast_header
│ │ │ │ │ │ └── weekly_forecast_header.dart
│ │ │ │ ├── toast
│ │ │ │ │ └── custom_toast.dart
│ │ │ │ ├── AppBar
│ │ │ │ │ ├── appbar.dart
│ │ │ │ │ └── settings_button.dart
│ │ │ │ └── bottom_sheet
│ │ │ │ │ ├── widgets
│ │ │ │ │ ├── bottom_sheet_list_view.dart
│ │ │ │ │ ├── bottom_sheet_text_field.dart
│ │ │ │ │ └── city_card.dart
│ │ │ │ │ └── bottom_sheet.dart
│ │ │ ├── date
│ │ │ │ ├── date_card.dart
│ │ │ │ └── date.dart
│ │ │ ├── animations
│ │ │ │ ├── right_animation.dart
│ │ │ │ └── top_animation.dart
│ │ │ ├── weather_card
│ │ │ │ ├── card_item.dart
│ │ │ │ ├── weather_card_template.dart
│ │ │ │ └── weather_card.dart
│ │ │ ├── weekly_forecast
│ │ │ │ ├── forecast_card.dart
│ │ │ │ └── weekly_forecast.dart
│ │ │ └── menu
│ │ │ │ └── menu.dart
│ │ ├── pages
│ │ │ ├── loading_page.dart
│ │ │ └── weather_page.dart
│ │ └── functions
│ │ │ └── build_carousel_slider.dart
│ │ └── data
│ │ ├── repository
│ │ ├── storage_repository_implementation.dart
│ │ └── weather_repository_implementation.dart
│ │ ├── models
│ │ ├── forecast_weather_model.dart
│ │ └── weather_model.dart
│ │ └── data_sources
│ │ ├── remote
│ │ ├── api_service.dart
│ │ └── api_service.g.dart
│ │ └── local
│ │ └── storage.dart
├── config
│ └── theme
│ │ ├── app_themes.dart
│ │ └── custom_colors.dart
└── main.dart
├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-50x50@1x.png
│ │ │ ├── Icon-App-50x50@2x.png
│ │ │ ├── Icon-App-57x57@1x.png
│ │ │ ├── Icon-App-57x57@2x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-72x72@1x.png
│ │ │ ├── Icon-App-72x72@2x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ └── project.pbxproj
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── RunnerTests
│ └── RunnerTests.swift
└── .gitignore
├── assets
└── icons
│ ├── arrow.png
│ ├── drop.png
│ ├── eye.png
│ ├── wind.png
│ ├── weather
│ ├── mist.png
│ ├── rain.png
│ ├── snow.png
│ ├── sun.png
│ ├── cloud.png
│ ├── unknown.png
│ ├── lightning.png
│ └── partly-cloudy.png
│ └── app_icon
│ └── icon.png
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── tortamque
│ │ │ │ │ └── weathque
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── .gitignore
├── analysis_options.yaml
├── .metadata
├── pubspec.yaml
├── README.md
└── LICENSE
/lib/core/.gitignore:
--------------------------------------------------------------------------------
1 | sensitive.dart
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/assets/icons/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/arrow.png
--------------------------------------------------------------------------------
/assets/icons/drop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/drop.png
--------------------------------------------------------------------------------
/assets/icons/eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/eye.png
--------------------------------------------------------------------------------
/assets/icons/wind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/wind.png
--------------------------------------------------------------------------------
/assets/icons/weather/mist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/mist.png
--------------------------------------------------------------------------------
/assets/icons/weather/rain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/rain.png
--------------------------------------------------------------------------------
/assets/icons/weather/snow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/snow.png
--------------------------------------------------------------------------------
/assets/icons/weather/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/sun.png
--------------------------------------------------------------------------------
/assets/icons/app_icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/app_icon/icon.png
--------------------------------------------------------------------------------
/assets/icons/weather/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/cloud.png
--------------------------------------------------------------------------------
/assets/icons/weather/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/unknown.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/assets/icons/weather/lightning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/lightning.png
--------------------------------------------------------------------------------
/assets/icons/weather/partly-cloudy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/assets/icons/weather/partly-cloudy.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tortamque/Weathque/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/lib/core/constants.dart:
--------------------------------------------------------------------------------
1 | const String currentWeatherUrl = "https://api.openweathermap.org/data/2.5/weather";
2 | const String forecastWeatherUrl = "https://api.openweathermap.org/data/2.5/forecast";
--------------------------------------------------------------------------------
/lib/features/app/domain/entities/cities_enum.dart:
--------------------------------------------------------------------------------
1 | enum City {
2 | paris('Paris'),
3 | newYork('New York'),
4 | sydney('Sydney');
5 |
6 | final String string;
7 |
8 | const City(this.string);
9 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/tortamque/weathque/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tortamque.weathque
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_current_weather/get_current_weather_event.dart:
--------------------------------------------------------------------------------
1 | abstract class GetCurrentWeatherEvent{
2 | const GetCurrentWeatherEvent();
3 | }
4 |
5 | class GetCurrentWeather extends GetCurrentWeatherEvent{
6 | const GetCurrentWeather();
7 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/repository/storage_repository.dart:
--------------------------------------------------------------------------------
1 | abstract class StorageRepository{
2 | Future saveCity(String cityName, String colorValue);
3 | List getCities();
4 | List getColors();
5 | Future deleteCity(String cityName);
6 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_event.dart:
--------------------------------------------------------------------------------
1 | abstract class GetWeatherForecastEvent{
2 | const GetWeatherForecastEvent();
3 | }
4 |
5 | class GetWeatherForecast extends GetWeatherForecastEvent{
6 | const GetWeatherForecast();
7 | }
--------------------------------------------------------------------------------
/lib/config/theme/app_themes.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ThemeData theme(){
4 | return ThemeData(
5 | useMaterial3: true,
6 | textSelectionTheme: TextSelectionThemeData(
7 | selectionHandleColor: Colors.black
8 | )
9 | );
10 | }
--------------------------------------------------------------------------------
/lib/config/theme/custom_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | enum CustomColors{
4 | yellow(Color(0xFFFFE142)),
5 | blue(Color(0xFF42C6FF)),
6 | pink(Color(0xFFFF64D4));
7 |
8 | final Color color;
9 |
10 | const CustomColors(this.color);
11 | }
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/features/app/domain/repository/weather_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
2 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
3 |
4 | abstract class WeatherRepository{
5 | Future getCurrentWeather(String cityName);
6 | }
7 |
8 | abstract class ForecastWeatherRepository{
9 | Future getForecast(String cityName);
10 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/get_cities.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/repository/storage_repository.dart';
3 |
4 | class GetCitiesUseCaseImplementation implements GetCitiesUseCase{
5 | final StorageRepository _storage;
6 |
7 | GetCitiesUseCaseImplementation(this._storage);
8 |
9 | @override
10 | List call() {
11 | return _storage.getCities();
12 | }
13 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/get_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/repository/storage_repository.dart';
3 |
4 | class GetColorsUseCaseImplementation implements GetColorsUseCase{
5 | final StorageRepository _storage;
6 |
7 | GetColorsUseCaseImplementation(this._storage);
8 |
9 | @override
10 | List call() {
11 | return _storage.getColors();
12 | }
13 | }
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/add_city/cities_changed_cubit.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:weathque/core/dependency_injection.dart';
3 | import 'package:weathque/features/app/domain/usecases/get_cities.dart';
4 |
5 | class CitiesChangedCubit extends Cubit>{
6 | CitiesChangedCubit() : super(locator()());
7 |
8 | void call(){
9 | emit(locator()());
10 | }
11 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/delete_city.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/repository/storage_repository.dart';
3 |
4 | class DeleteCityUseCaseImplementation implements DeleteCityUseCase{
5 | final StorageRepository _storage;
6 |
7 | DeleteCityUseCaseImplementation(this._storage);
8 |
9 | @override
10 | Future call(String cityName) {
11 | return _storage.deleteCity(cityName);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/core/usecases/usecase.dart:
--------------------------------------------------------------------------------
1 | abstract class UseCase{
2 | Future call({required String cityName});
3 | }
4 |
5 | abstract class SaveCityUseCase{
6 | Future call({required String cityName, required String colorValue});
7 | }
8 |
9 | abstract class GetCitiesUseCase{
10 | List call();
11 | }
12 |
13 | abstract class GetColorsUseCase{
14 | List call();
15 | }
16 |
17 | abstract class DeleteCityUseCase{
18 | Future call(String cityName);
19 | }
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/save_city.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/repository/storage_repository.dart';
3 |
4 | class SaveCityUseCaseImplementation implements SaveCityUseCase{
5 | final StorageRepository _storage;
6 |
7 | SaveCityUseCaseImplementation(this._storage);
8 |
9 | @override
10 | Future call({required String cityName, required String colorValue}) {
11 | return _storage.saveCity(cityName, colorValue);
12 | }
13 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/summary_text/summary_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SummaryText extends StatelessWidget {
4 | final String text;
5 | final Color color;
6 |
7 | const SummaryText({super.key, required this.text, required this.color});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Text(
12 | text,
13 | style: TextStyle(
14 | fontWeight: FontWeight.bold,
15 | color: color
16 | ),
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/temperature/temperature_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TemperatureText extends StatelessWidget {
4 | final String text;
5 | final Color color;
6 |
7 | const TemperatureText({super.key, required this.text, required this.color});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Text(
12 | text,
13 | style: TextStyle(
14 | color: color,
15 | fontWeight: FontWeight.w400
16 | )
17 | );
18 | }
19 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/condition/condition_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ConditionText extends StatelessWidget {
4 | final String text;
5 | final Color color;
6 |
7 | const ConditionText({super.key, required this.text, required this.color});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Text(
12 | text,
13 | style: TextStyle(
14 | color: color,
15 | fontWeight: FontWeight.bold,
16 | fontSize: 16
17 | ),
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/headers/summary_header/header_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class HeaderText extends StatelessWidget {
4 | final String text;
5 | final Color color;
6 |
7 | const HeaderText({super.key, required this.text, required this.color});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Text(
12 | text,
13 | style: TextStyle(
14 | fontWeight: FontWeight.w800,
15 | fontSize: 18,
16 | color: color
17 | ),
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/get_current_weather.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
3 | import 'package:weathque/features/app/domain/repository/weather_repository.dart';
4 |
5 | class GetCurrentWeatherUseCase implements UseCase{
6 | final WeatherRepository _weatherRepository;
7 | GetCurrentWeatherUseCase(this._weatherRepository);
8 |
9 | @override
10 | Future call({required String cityName}) {
11 | return _weatherRepository.getCurrentWeather(cityName);
12 | }
13 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/entities/summary_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
2 |
3 | class SummaryBuilder{
4 | String call(WeatherEntity weatherEntity){
5 | String result = """Now it feels like +${weatherEntity.information.feelsLike!.round()}°, actually +${weatherEntity.information.temp!.round()}°.
6 | It feels that way because of the ${weatherEntity.weather[0].description}.
7 | Today, the temperature is felt in the range from +${weatherEntity.information.tempMin!.round()}° to ${weatherEntity.information.tempMax!.round()}°.""";
8 |
9 | return result;
10 | }
11 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_current_weather/get_current_weather_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
2 |
3 | abstract class GetCurrentWeatherState{
4 | final Map? weatherEntity;
5 |
6 | const GetCurrentWeatherState({
7 | this.weatherEntity
8 | });
9 | }
10 |
11 | class GetCurrentWeatherLoading extends GetCurrentWeatherState{
12 | const GetCurrentWeatherLoading();
13 | }
14 |
15 | class GetCurrentWeatherDone extends GetCurrentWeatherState{
16 | const GetCurrentWeatherDone(Map weatherEntity) : super(weatherEntity: weatherEntity);
17 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/usecases/get_weather_forecast.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/usecases/usecase.dart';
2 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
3 | import 'package:weathque/features/app/domain/repository/weather_repository.dart';
4 |
5 | class GetWeatherForecastUseCase implements UseCase{
6 | final ForecastWeatherRepository _forecastWeatherRepository;
7 |
8 | GetWeatherForecastUseCase(this._forecastWeatherRepository);
9 |
10 | @override
11 | Future call({required String cityName}) {
12 | return _forecastWeatherRepository.getForecast(cityName);
13 | }
14 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/widgets/cities_cubit_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart';
4 |
5 | class CitiesCubitWidget extends StatelessWidget {
6 | final Function(List state) onSuccess;
7 |
8 | const CitiesCubitWidget({super.key, required this.onSuccess});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return BlocBuilder>(
13 | builder: (_, cubitCities) {
14 | return onSuccess(cubitCities);
15 | },
16 | );
17 | }
18 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
2 |
3 | abstract class GetWeatherForecastState{
4 | final Map? forecastWeatherEntity;
5 |
6 | const GetWeatherForecastState({
7 | this.forecastWeatherEntity
8 | });
9 | }
10 |
11 | class GetWeatherForecastLoading extends GetWeatherForecastState{
12 | const GetWeatherForecastLoading();
13 | }
14 |
15 | class GetWeatherForecastDone extends GetWeatherForecastState{
16 | const GetWeatherForecastDone(Map? forecastWeatherEntity):super(forecastWeatherEntity: forecastWeatherEntity);
17 | }
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.3.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | tasks.register("clean", Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/pages/loading_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/misc/AppBar/appbar.dart';
3 |
4 | class LoadingPage extends StatelessWidget {
5 | final Color color;
6 |
7 | const LoadingPage({super.key, required this.color});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | backgroundColor: color,
13 | appBar: CustomAppBar(title: "Loading", bottomSheetColor: color),
14 | body: const Center(
15 | child: SizedBox(
16 | width: 100,
17 | height: 100,
18 | child: CircularProgressIndicator(
19 | color: Colors.black,
20 | strokeWidth: 6.0,
21 | ),
22 | ),
23 | ),
24 | );
25 | }
26 | }
--------------------------------------------------------------------------------
/lib/features/app/data/repository/storage_repository_implementation.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/data/data_sources/local/storage.dart';
2 | import 'package:weathque/features/app/domain/repository/storage_repository.dart';
3 |
4 | class StorageRepositoryImplementation implements StorageRepository{
5 | final Storage _storage;
6 |
7 | StorageRepositoryImplementation(this._storage);
8 |
9 | @override
10 | Future saveCity(String cityName, String colorValue) async{
11 | return await _storage.saveCity(cityName, colorValue);
12 | }
13 |
14 | @override
15 | List getCities() {
16 | return _storage.getCities();
17 | }
18 |
19 | @override
20 | List getColors() {
21 | return _storage.getColors();
22 | }
23 |
24 | @override
25 | Future deleteCity(String cityName) async {
26 | await _storage.deleteCity(cityName);
27 | }
28 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/features/app/data/models/forecast_weather_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
2 |
3 | class ForecastWeatherModel extends ForecastWeatherEntity {
4 | ForecastWeatherModel({
5 | required cod,
6 | required message,
7 | required cnt,
8 | required list,
9 | required city,
10 | }):super(
11 | cod: cod,
12 | message: message,
13 | cnt: cnt,
14 | list: list,
15 | city: city
16 | );
17 |
18 | factory ForecastWeatherModel.fromJson(Map json) {
19 | List weatherDataList = (json['list'] as List)
20 | .map((data) => WeatherData.fromJson(data))
21 | .toList();
22 |
23 | return ForecastWeatherModel(
24 | cod: json['cod'],
25 | message: json['message'],
26 | cnt: json['cnt'],
27 | list: weatherDataList,
28 | city: ForecastCity.fromJson(json['city']),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/date/date_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DateCard extends StatelessWidget {
4 | final Color backgroundColor;
5 | final Color textColor;
6 | final String text;
7 |
8 | const DateCard({
9 | required this.backgroundColor,
10 | required this.textColor,
11 | required this.text,
12 | super.key
13 | });
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Container(
18 | decoration: BoxDecoration(
19 | borderRadius: const BorderRadius.all(Radius.circular(20)),
20 | color: backgroundColor
21 | ),
22 | child: Padding(
23 | padding: const EdgeInsets.symmetric(
24 | horizontal: 15,
25 | vertical: 5
26 | ),
27 | child: Text(
28 | text,
29 | style: TextStyle(
30 | color: textColor,
31 | ),
32 | ),
33 | ),
34 | );
35 | }
36 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/animations/right_animation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RightAnimation extends StatelessWidget {
4 | final Widget child;
5 | final bool animationField;
6 | final Duration duration;
7 | final Curve curve;
8 | final double positionInitialValue;
9 | final double opacityInitialValue;
10 |
11 | const RightAnimation({super.key, required this.child, required this.animationField, required this.duration, required this.curve, required this.positionInitialValue, required this.opacityInitialValue});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return AnimatedPositioned(
16 | duration: duration,
17 | curve: curve,
18 | right: !animationField ? positionInitialValue : 0,
19 | child: AnimatedOpacity(
20 | duration: duration,
21 | curve: curve,
22 | opacity: !animationField ? opacityInitialValue : 1,
23 | child: child
24 | ),
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/toast/custom_toast.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomToast extends StatelessWidget {
4 | final String text;
5 | final bool isError;
6 |
7 | const CustomToast({super.key, required this.text, required this.isError});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Container(
12 | padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
13 | decoration: BoxDecoration(
14 | borderRadius: BorderRadius.circular(25.0),
15 | color: isError ? Colors.redAccent : Colors.greenAccent,
16 | ),
17 | child: Row(
18 | mainAxisSize: MainAxisSize.min,
19 | children: [
20 | Icon(isError ? Icons.error_outline : Icons.check),
21 | SizedBox(
22 | width: 12.0,
23 | ),
24 | Flexible(
25 | child: Text(text)
26 | ),
27 | ],
28 | ),
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/animations/top_animation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TopAnimation extends StatelessWidget {
4 | final Widget child;
5 | final bool animationField;
6 | final Duration duration;
7 | final Curve curve;
8 | final double positionInitialValue;
9 | final double opacityInitialValue;
10 |
11 | const TopAnimation({super.key, required this.child, required this.animationField, required this.duration, required this.curve, required this.positionInitialValue, required this.opacityInitialValue});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return AnimatedPositioned(
16 | duration: duration,
17 | curve: curve,
18 | top: !animationField ? positionInitialValue : 0,
19 | left: 0,
20 | right: 0,
21 | child: AnimatedOpacity(
22 | duration: duration,
23 | curve: curve,
24 | opacity: !animationField ? opacityInitialValue : 1,
25 | child: child,
26 | ),
27 | );
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/temperature/temperature_animation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TemperatureAnimation extends StatelessWidget {
4 | final Widget child;
5 | final bool isAnimated;
6 | final Duration animationDuration;
7 | final Curve animationCurve;
8 | final double positionInitialValue;
9 | final double opacityInitialValue;
10 |
11 | const TemperatureAnimation({super.key, required this.child, required this.isAnimated, required this.animationDuration, required this.animationCurve, required this.positionInitialValue, required this.opacityInitialValue});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return AnimatedPositioned(
16 | duration: animationDuration,
17 | curve: animationCurve,
18 | top: !isAnimated ? positionInitialValue : 0,
19 | child: AnimatedOpacity(
20 | duration: animationDuration,
21 | curve: animationCurve,
22 | opacity: !isAnimated ? opacityInitialValue : 1,
23 | child: child
24 | ),
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/features/app/data/data_sources/remote/api_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:retrofit/retrofit.dart';
2 | import 'package:weathque/core/constants.dart';
3 | // ignore: depend_on_referenced_packages
4 | import 'package:dio/dio.dart';
5 | import 'package:weathque/features/app/data/models/forecast_weather_model.dart';
6 | import 'package:weathque/features/app/data/models/weather_model.dart';
7 | part 'api_service.g.dart';
8 |
9 | @RestApi(baseUrl: currentWeatherUrl)
10 | abstract class CurrentWeatherApiService{
11 | factory CurrentWeatherApiService(Dio dio) = _CurrentWeatherApiService;
12 |
13 | @GET("")
14 | Future> getCurrentWeather({
15 | @Query("q") required String cityName,
16 | @Query("appid") required String apiKey,
17 | @Query("units") String units = "metric"
18 | });
19 | }
20 |
21 | @RestApi(baseUrl: forecastWeatherUrl)
22 | abstract class ForecastWeatherApiService{
23 | factory ForecastWeatherApiService(Dio dio) = _ForecastWeatherApiService;
24 |
25 | @GET("")
26 | Future> getForecast({
27 | @Query("q") required String cityName,
28 | @Query("appid") required String apiKey,
29 | @Query("units") String units = "metric"
30 | });
31 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/widgets/forecast_bloc_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/config/theme/custom_colors.dart';
4 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_bloc.dart';
5 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_state.dart';
6 | import 'package:weathque/features/app/presentation/pages/loading_page.dart';
7 |
8 | class ForecastBlocWidget extends StatelessWidget {
9 | final Function(GetWeatherForecastState state) onSuccess;
10 |
11 | const ForecastBlocWidget({super.key, required this.onSuccess});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return BlocBuilder(
16 | builder: (_, forecastWeatherState) {
17 | if(forecastWeatherState is GetWeatherForecastLoading){
18 | return LoadingPage(color: CustomColors.yellow.color);
19 | }
20 | if(forecastWeatherState is GetWeatherForecastDone){
21 | return onSuccess(forecastWeatherState);
22 | }
23 | return const SizedBox();
24 | },
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/widgets/current_weather_bloc_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/config/theme/custom_colors.dart';
4 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_bloc.dart';
5 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_state.dart';
6 | import 'package:weathque/features/app/presentation/pages/loading_page.dart';
7 |
8 | class CurrentWeatherForecastBloc extends StatelessWidget {
9 | final Function(GetCurrentWeatherState state) onSuccess;
10 |
11 | const CurrentWeatherForecastBloc({super.key, required this.onSuccess});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return BlocBuilder(
16 | builder: (_, currentWeatherState) {
17 | if(currentWeatherState is GetCurrentWeatherLoading){
18 | return LoadingPage(color: CustomColors.yellow.color);
19 | }
20 | if(currentWeatherState is GetCurrentWeatherDone){
21 | return onSuccess(currentWeatherState);
22 | }
23 | return const SizedBox();
24 | }
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/AppBar/appbar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:weathque/features/app/presentation/widgets/misc/AppBar/settings_button.dart';
4 |
5 | class CustomAppBar extends StatelessWidget implements PreferredSizeWidget{
6 | final String title;
7 | final Color bottomSheetColor;
8 |
9 | const CustomAppBar({required this.title, super.key, required this.bottomSheetColor});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return AppBar(
14 | backgroundColor: Colors.transparent,
15 | elevation: 0,
16 | centerTitle: true,
17 | actions: [
18 | SettingsButton(bottomSheetColor: bottomSheetColor)
19 | ],
20 | title: Text(
21 | title,
22 | style: const TextStyle(
23 | fontWeight: FontWeight.bold
24 | ),
25 | ),
26 | systemOverlayStyle: SystemUiOverlayStyle(
27 | statusBarColor: Colors.transparent,
28 | statusBarIconBrightness: Brightness.dark,
29 | statusBarBrightness: Brightness.dark,
30 | ),
31 | );
32 | }
33 |
34 | @override
35 | Size get preferredSize => const Size.fromHeight(kToolbarHeight);
36 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/bottom_sheet/widgets/bottom_sheet_list_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart';
4 | import 'package:weathque/features/app/presentation/widgets/misc/bottom_sheet/widgets/city_card.dart';
5 |
6 | class BottomSheetListView extends StatelessWidget {
7 | const BottomSheetListView({super.key});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Expanded(
12 | child: BlocBuilder>(
13 | builder: (context, cubitCities) {
14 | return Padding(
15 | padding: const EdgeInsets.only(top: 10),
16 | child: ListView.builder(
17 | itemCount: cubitCities.length,
18 | itemBuilder: (context, index) {
19 | if(cubitCities.length > 1){
20 | return CityCard(name: cubitCities[index], index: index, isLast: false);
21 | } else{
22 | return CityCard(name: cubitCities[index], index: index, isLast: true);
23 | }
24 | },
25 | ),
26 | );
27 | },
28 | ),
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/pages/weather_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
3 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
4 | import 'package:weathque/features/app/presentation/widgets/menu/menu.dart';
5 | import 'package:weathque/features/app/presentation/widgets/misc/AppBar/appbar.dart';
6 |
7 | class WeatherPage extends StatelessWidget {
8 | final WeatherEntity? weatherEntity;
9 | final ForecastWeatherEntity? forecastWeatherEntity;
10 | final Color color;
11 | final String city;
12 |
13 | const WeatherPage({
14 | required this.weatherEntity,
15 | required this.forecastWeatherEntity,
16 | required this.color,
17 | super.key, required this.city
18 | });
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Scaffold(
23 | resizeToAvoidBottomInset: false,
24 | backgroundColor: color,
25 | appBar: CustomAppBar(title: city, bottomSheetColor: color),
26 | body: Row(
27 | children: [
28 | const Spacer(flex: 1,),
29 | Menu(
30 | weatherEntity: weatherEntity,
31 | forecastWeatherEntity: forecastWeatherEntity,
32 | color: color,
33 | ),
34 | const Spacer(flex: 1,),
35 | ],
36 | ),
37 | );
38 | }
39 | }
--------------------------------------------------------------------------------
/lib/features/app/data/repository/weather_repository_implementation.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/core/sensitive.dart';
2 | import 'package:weathque/features/app/data/data_sources/remote/api_service.dart';
3 | import 'package:weathque/features/app/data/models/weather_model.dart';
4 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
5 | import 'package:weathque/features/app/domain/repository/weather_repository.dart';
6 |
7 | class WeatherRepositoryImplementation implements WeatherRepository{
8 | final CurrentWeatherApiService _currentWeatherApiService;
9 |
10 | WeatherRepositoryImplementation(this._currentWeatherApiService);
11 |
12 | @override
13 | Future getCurrentWeather(String cityName) async {
14 | final httpResponse = await _currentWeatherApiService.getCurrentWeather(cityName: cityName, apiKey: apiKey);
15 |
16 | return httpResponse.data;
17 | }
18 | }
19 |
20 | class ForecastWeatherRepositoryImplementation implements ForecastWeatherRepository{
21 | final ForecastWeatherApiService _forecastWeatherApiService;
22 |
23 | ForecastWeatherRepositoryImplementation(this._forecastWeatherApiService);
24 |
25 | @override
26 | Future getForecast(String cityName) async {
27 | final httpResponse = await _forecastWeatherApiService.getForecast(cityName: cityName, apiKey: apiKey);
28 |
29 | return httpResponse.data;
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/weather_card/card_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CardItem extends StatelessWidget {
4 | final String iconPath;
5 | final String data;
6 | final String type;
7 | final Color color;
8 |
9 | const CardItem({
10 | required this.iconPath,
11 | required this.data,
12 | required this.type,
13 | required this.color,
14 | super.key
15 | });
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Expanded(
20 | flex: 1,
21 | child: Padding(
22 | padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 15),
23 | child: Column(
24 | mainAxisAlignment: MainAxisAlignment.spaceAround,
25 | children: [
26 | Image.asset(
27 | iconPath,
28 | height: 40,
29 | width: 40,
30 | color: color
31 | ),
32 | Text(
33 | data,
34 | style: TextStyle(
35 | color: color,
36 | fontWeight: FontWeight.bold,
37 | fontSize: 16
38 | ),
39 | ),
40 | Text(
41 | type,
42 | style: TextStyle(
43 | color: color,
44 | fontSize: 12
45 | ),
46 | ),
47 | ],
48 | ),
49 | ),
50 | );
51 | }
52 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/functions/build_carousel_slider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_carousel_slider/carousel_slider.dart';
3 | import 'package:weathque/core/dependency_injection.dart';
4 | import 'package:weathque/features/app/domain/usecases/get_cities.dart';
5 | import 'package:weathque/features/app/domain/usecases/get_colors.dart';
6 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_state.dart';
7 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_state.dart';
8 | import 'package:weathque/features/app/presentation/pages/weather_page.dart';
9 |
10 | Widget buildCarouselSlider(GetCurrentWeatherState currentWeatherState, GetWeatherForecastState forecastWeatherState){
11 | List cities = locator()();
12 | List colors = locator()();
13 |
14 | return CarouselSlider(
15 | slideTransform: const CubeTransform(),
16 | unlimitedMode: true,
17 | children: [
18 | for (int i = 0; i < cities.length; i++)
19 | WeatherPage(
20 | weatherEntity: currentWeatherState.weatherEntity![cities[i]]!,
21 | forecastWeatherEntity: forecastWeatherState.forecastWeatherEntity![cities[i]]!,
22 | color: Color(int.parse(colors[i])),
23 | city: cities[i],
24 | ),
25 | ],
26 | );
27 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/weather_card/weather_card_template.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/weather_card/card_item.dart';
3 |
4 | class WeatherCardTemplate extends StatelessWidget {
5 | final String windSpeed;
6 | final String humidity;
7 | final String visibility;
8 | final Color color;
9 |
10 | const WeatherCardTemplate({super.key, required this.windSpeed, required this.humidity, required this.visibility, required this.color});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Container(
15 | decoration: const BoxDecoration(
16 | borderRadius: BorderRadius.all(Radius.circular(10)),
17 | color: Colors.black
18 | ),
19 | height: 125,
20 | width: double.maxFinite,
21 | child: Row(
22 | children: [
23 | CardItem(
24 | color: color,
25 | iconPath: "assets/icons/wind.png",
26 | data: "${windSpeed}km/h",
27 | type: "Wind"
28 | ),
29 | CardItem(
30 | color: color,
31 | iconPath: "assets/icons/drop.png",
32 | data: "${humidity}%",
33 | type: "Humidity"
34 | ),
35 | CardItem(
36 | color: color,
37 | iconPath: "assets/icons/eye.png",
38 | data: "${visibility}km",
39 | type: "Visibility"
40 | ),
41 | ],
42 | ),
43 | );
44 | }
45 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/blocs_provider_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/core/dependency_injection.dart';
4 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart';
5 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_bloc.dart';
6 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_event.dart';
7 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_bloc.dart';
8 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_event.dart';
9 |
10 | class BlocsProviderBuilder extends StatelessWidget {
11 | final Widget child;
12 | final BuildContext context;
13 | final List providers = [
14 | BlocProvider(
15 | create: (buildContext) => locator()..add(const GetCurrentWeather())
16 | ),
17 | BlocProvider(
18 | create: (buildContext) => locator()..add(const GetWeatherForecast()),
19 | ),
20 | BlocProvider(
21 | create: (buildContext) => locator(),
22 | )
23 | ];
24 |
25 | BlocsProviderBuilder({super.key, required this.child, required this.context});
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return MultiBlocProvider(providers: providers, child: child);
30 | }
31 | }
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/lib/features/app/data/models/weather_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
2 |
3 | class WeatherModel extends WeatherEntity{
4 | WeatherModel({
5 | required coord,
6 | required weather,
7 | required base,
8 | required main,
9 | required visibility,
10 | required wind,
11 | required clouds,
12 | required dt,
13 | required sys,
14 | required timezone,
15 | required id,
16 | required name,
17 | required cod,
18 | }) : super(
19 | coord: coord,
20 | weather: weather,
21 | base: base,
22 | information: main,
23 | visibility: visibility,
24 | wind: wind,
25 | clouds: clouds,
26 | dt: dt,
27 | sunInformation: sys,
28 | timezone: timezone,
29 | id: id,
30 | name: name,
31 | cod: cod
32 | );
33 |
34 | factory WeatherModel.fromJson(Map json) {
35 | return WeatherModel(
36 | coord: Coord.fromJson(json['coord']),
37 | weather: (json['weather'] as List)
38 | .map((item) => WeatherData.fromJson(item))
39 | .toList(),
40 | base: json['base'],
41 | main: Information.fromJson(json['main']),
42 | visibility: json['visibility'],
43 | wind: Wind.fromJson(json['wind']),
44 | clouds: Clouds.fromJson(json['clouds']),
45 | dt: json['dt'],
46 | sys: SunInformation.fromJson(json['sys']),
47 | timezone: json['timezone'],
48 | id: json['id'],
49 | name: json['name'],
50 | cod: json['cod'],
51 | );
52 | }
53 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/weekly_forecast/forecast_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ForecastCard extends StatelessWidget {
4 | final String temperature;
5 | final String iconPath;
6 | final String date;
7 |
8 | const ForecastCard({
9 | required this.temperature,
10 | required this.iconPath,
11 | required this.date,
12 | super.key
13 | });
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Container(
18 | padding: const EdgeInsets.symmetric(vertical: 5),
19 | decoration: BoxDecoration(
20 | border: Border.all(
21 | width: 3
22 | ),
23 | borderRadius: const BorderRadius.all(Radius.circular(10)),
24 | ),
25 | height: 110,
26 | width: 67.5,
27 | child: Column(
28 | mainAxisAlignment: MainAxisAlignment.spaceAround,
29 | children: [
30 | Text(
31 | "${temperature}°",
32 | style: const TextStyle(
33 | color: Colors.black,
34 | fontWeight: FontWeight.bold,
35 | fontSize: 16
36 | ),
37 | ),
38 | Image.asset(
39 | iconPath,
40 | width: 20,
41 | height: 20,
42 | ),
43 | Text(
44 | date,
45 | style: const TextStyle(
46 | color: Colors.black,
47 | fontWeight: FontWeight.bold,
48 | fontSize: 12
49 | ),
50 | ),
51 | ],
52 | ),
53 | );
54 | }
55 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/AppBar/settings_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/misc/bottom_sheet/bottom_sheet.dart';
3 |
4 | class SettingsButton extends StatefulWidget {
5 | final Color bottomSheetColor;
6 |
7 | const SettingsButton({super.key, required this.bottomSheetColor});
8 |
9 | @override
10 | State createState() => _SettingsButtonState();
11 | }
12 |
13 | class _SettingsButtonState extends State with TickerProviderStateMixin {
14 | late AnimationController _controller;
15 | late Animation _animation;
16 |
17 | @override
18 | void initState() {
19 | _controller = AnimationController(
20 | duration: const Duration(milliseconds: 500),
21 | vsync: this,
22 | );
23 | _animation = CurvedAnimation(
24 | parent: _controller,
25 | curve: Curves.easeOutSine
26 | );
27 |
28 | super.initState();
29 | }
30 |
31 | @override
32 | void dispose() {
33 | _controller.dispose();
34 | super.dispose();
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | return RotationTransition(
40 | turns: _animation,
41 | child: IconButton(
42 | icon: Icon(
43 | Icons.settings,
44 | color: Colors.black,
45 | ),
46 | onPressed: (){
47 | _controller.reset();
48 | _controller.forward();
49 |
50 | showCustomBottomSheet(context, widget.bottomSheetColor);
51 | },
52 | ),
53 | );
54 | }
55 |
56 |
57 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/bottom_sheet/widgets/bottom_sheet_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 |
4 | class BottomSheetTextField extends StatelessWidget {
5 | final TextEditingController controller;
6 | final FToast toastManager;
7 | final Function(FToast toastManager, BuildContext context) onSubmit;
8 |
9 | const BottomSheetTextField({super.key, required this.controller, required this.toastManager, required this.onSubmit});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return TextField(
14 | controller: controller,
15 | onSubmitted: (value) {
16 | onSubmit(toastManager, context);
17 | },
18 | textCapitalization: TextCapitalization.words,
19 | cursorColor: Colors.black,
20 | decoration: InputDecoration(
21 | enabledBorder: OutlineInputBorder(
22 | borderRadius: BorderRadius.all(Radius.circular(20.0)),
23 | borderSide: BorderSide(color: Colors.black)
24 | ),
25 | focusedBorder: OutlineInputBorder(
26 | borderRadius: BorderRadius.all(Radius.circular(20.0)),
27 | borderSide: BorderSide(color: Colors.black)
28 | ),
29 | hintText: "Enter a city",
30 | labelText: "City",
31 | labelStyle: TextStyle(
32 | color: Colors.black
33 | ),
34 | suffixIcon: IconButton(
35 | icon: Icon(Icons.search, color: Colors.black),
36 | onPressed: () {
37 | onSubmit(toastManager, context);
38 | },
39 | )
40 | ),
41 | );
42 | }
43 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_current_weather/get_current_weather_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:weathque/core/dependency_injection.dart';
3 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
4 | import 'package:weathque/features/app/domain/usecases/get_cities.dart';
5 | import 'package:weathque/features/app/domain/usecases/get_current_weather.dart';
6 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_event.dart';
7 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_state.dart';
8 |
9 | class GetCurrentWeatherBloc extends Bloc{
10 | final GetCurrentWeatherUseCase _getCurrentWeatherUseCase;
11 |
12 | GetCurrentWeatherBloc(this._getCurrentWeatherUseCase): super(const GetCurrentWeatherLoading()){
13 | on(onGetCurrentWeather);
14 | }
15 |
16 | void onGetCurrentWeather(GetCurrentWeather event, Emitter emitter) async{
17 | // ignore: invalid_use_of_visible_for_testing_member
18 | emit(const GetCurrentWeatherLoading());
19 |
20 | List cities = locator()();
21 | Map entities = {};
22 |
23 | for (var city in cities) {
24 | final dataState = await _getCurrentWeatherUseCase.call(cityName: city);
25 |
26 | entities[city] = dataState;
27 | }
28 |
29 | // ignore: invalid_use_of_visible_for_testing_member
30 | emit(
31 | GetCurrentWeatherDone(entities)
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:weathque/core/dependency_injection.dart';
3 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
4 | import 'package:weathque/features/app/domain/usecases/get_cities.dart';
5 | import 'package:weathque/features/app/domain/usecases/get_weather_forecast.dart';
6 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_event.dart';
7 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_state.dart';
8 |
9 | class GetWeatherForecastBloc extends Bloc{
10 | final GetWeatherForecastUseCase _getWeatherForecastUseCase;
11 |
12 | GetWeatherForecastBloc(this._getWeatherForecastUseCase): super(const GetWeatherForecastLoading()){
13 | on(onGetWeatherForecast);
14 | }
15 |
16 | void onGetWeatherForecast(GetWeatherForecast event, Emitter emitter) async{
17 | // ignore: invalid_use_of_visible_for_testing_member
18 | emit(const GetWeatherForecastLoading());
19 |
20 | List cities = locator()();
21 | Map entities = {};
22 |
23 | for (var city in cities) {
24 | final dataState = await _getWeatherForecastUseCase.call(cityName: city);
25 |
26 | entities[city] = dataState;
27 | }
28 |
29 | // ignore: invalid_use_of_visible_for_testing_member
30 | emit(
31 | GetWeatherForecastDone(entities)
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/headers/summary_header/header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/misc/headers/summary_header/header_text.dart';
4 |
5 | class Header extends StatefulWidget {
6 | final String text;
7 |
8 | const Header({
9 | required this.text,
10 | super.key
11 | });
12 |
13 | @override
14 | State createState() => _HeaderState();
15 | }
16 |
17 | class _HeaderState extends State {
18 | final Duration animationDuration = const Duration(milliseconds: 500);
19 | final Curve animationCurve = Curves.easeOutSine;
20 | bool isAnimated = false;
21 |
22 | @override
23 | void initState() {
24 | _startAnimation();
25 | super.initState();
26 | }
27 |
28 | void _startAnimation() async{
29 | await Future.delayed(const Duration(milliseconds: 1200))
30 | .then((value) => setState(() => isAnimated = true));
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Stack(
36 | children: [
37 | HeaderText(
38 | text: widget.text,
39 | color: Colors.transparent
40 | ),
41 |
42 | TopAnimation(
43 | curve: animationCurve,
44 | duration: animationDuration,
45 | animationField: isAnimated,
46 | positionInitialValue: MediaQuery.of(context).size.height/40,
47 | opacityInitialValue: 0,
48 | child: HeaderText(
49 | text: widget.text,
50 | color: Colors.black
51 | ),
52 | )
53 | ],
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttertoast/fluttertoast.dart';
3 | import 'package:weathque/config/theme/app_themes.dart';
4 | import 'package:weathque/core/dependency_injection.dart';
5 | import 'package:weathque/features/app/data/data_sources/local/storage.dart';
6 | import 'package:weathque/features/app/presentation/bloc/blocs_provider_builder.dart';
7 | import 'package:weathque/features/app/presentation/bloc/widgets/cities_cubit_widget.dart';
8 | import 'package:weathque/features/app/presentation/bloc/widgets/current_weather_bloc_widget.dart';
9 | import 'package:weathque/features/app/presentation/bloc/widgets/forecast_bloc_widget.dart';
10 | import 'features/app/presentation/functions/build_carousel_slider.dart';
11 |
12 | void main() async{
13 | WidgetsFlutterBinding.ensureInitialized();
14 | initializeDependencies();
15 | await locator().ensurePrefsInitialized();
16 | runApp(const WeathqueApp());
17 | }
18 |
19 | class WeathqueApp extends StatelessWidget {
20 | const WeathqueApp({super.key});
21 | @override
22 | Widget build(BuildContext context) {
23 | return BlocsProviderBuilder(
24 | context: context,
25 | child: MaterialApp(
26 | builder: FToastBuilder(),
27 | theme: theme(),
28 | home: CitiesCubitWidget(
29 | onSuccess: (state) {
30 | return CurrentWeatherForecastBloc(
31 | onSuccess: (currentWeatherState) {
32 | return ForecastBlocWidget(
33 | onSuccess: (forecastWeatherState) {
34 | return buildCarouselSlider(currentWeatherState, forecastWeatherState);
35 | },
36 | );
37 | },
38 | );
39 | },
40 | )
41 | ),
42 | );
43 | }
44 | }
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
17 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
18 | - platform: android
19 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
20 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
21 | - platform: ios
22 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
23 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
24 | - platform: linux
25 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
26 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
27 | - platform: macos
28 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
29 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
30 | - platform: web
31 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
32 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
33 | - platform: windows
34 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
35 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Weathque
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | Weathque
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/condition/condition.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/condition/condition_text.dart';
4 |
5 | class Condition extends StatefulWidget {
6 | final String condition;
7 |
8 | const Condition({
9 | required this.condition,
10 | super.key
11 | });
12 |
13 | @override
14 | State createState() => _ConditionState();
15 | }
16 |
17 | class _ConditionState extends State {
18 | final Duration animationDuration = const Duration(milliseconds: 500);
19 | final Curve animationCurve = Curves.easeOutSine;
20 | bool isAnimated = false;
21 |
22 | @override
23 | void initState() {
24 | _startAnimation();
25 | super.initState();
26 | }
27 |
28 | void _startAnimation() async{
29 | await Future.delayed(const Duration(milliseconds: 500))
30 | .then((value) => setState(() => isAnimated = true));
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Center(
36 | child: Padding(
37 | padding: const EdgeInsets.symmetric(vertical: 10),
38 | child: Stack(
39 | children: [
40 | ConditionText(
41 | text: widget.condition,
42 | color: Colors.transparent,
43 | ),
44 |
45 | TopAnimation(
46 | animationField: isAnimated,
47 | duration: animationDuration,
48 | curve: animationCurve,
49 | positionInitialValue: MediaQuery.of(context).size.height/40,
50 | opacityInitialValue: 0,
51 | child: ConditionText(
52 | text: widget.condition,
53 | color: Colors.black
54 | ),
55 | )
56 | ],
57 | ),
58 | ),
59 | );
60 | }
61 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/weather_card/weather_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/weather_card/weather_card_template.dart';
4 |
5 | class WeatherCard extends StatefulWidget {
6 | final String windSpeed;
7 | final String humidity;
8 | final String visibility;
9 | final Color color;
10 |
11 | const WeatherCard({
12 | super.key,
13 | required this.windSpeed,
14 | required this.humidity,
15 | required this.visibility, required this.color
16 | });
17 |
18 | @override
19 | State createState() => _WeatherCardState();
20 | }
21 |
22 | class _WeatherCardState extends State {
23 | final Duration animationDuration = const Duration(milliseconds: 800);
24 | final Curve animationCurve = Curves.easeOutSine;
25 | bool isAnimated = false;
26 |
27 | @override
28 | void initState() {
29 | _startAnimation();
30 | super.initState();
31 | }
32 |
33 | void _startAnimation() async{
34 | await Future.delayed(const Duration(milliseconds: 2000))
35 | .then((value) => setState(() => isAnimated = true));
36 | }
37 | @override
38 | Widget build(BuildContext context) {
39 | return SizedBox(
40 | height: 125,
41 | child: Stack(
42 | children: [
43 | TopAnimation(
44 | animationField: isAnimated,
45 | duration: animationDuration,
46 | curve: animationCurve,
47 | positionInitialValue: MediaQuery.of(context).size.height/40,
48 | opacityInitialValue: 0,
49 | child: WeatherCardTemplate(
50 | color: widget.color,
51 | humidity: widget.humidity,
52 | visibility: widget.visibility,
53 | windSpeed: widget.windSpeed,
54 | ),
55 | )
56 | ],
57 | ),
58 | );
59 | }
60 | }
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/features/app/data/data_sources/local/storage.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | abstract class Storage {
4 | SharedPreferences? prefs;
5 | Future saveCity(String cityName, String colorValue);
6 | List getCities();
7 | List getColors();
8 | Future deleteCity(String cityName);
9 | }
10 |
11 | class StorageImplementation implements Storage{
12 | SharedPreferences? prefs;
13 |
14 | StorageImplementation(){
15 | _initPrefs();
16 | }
17 |
18 | Future _initPrefs() async {
19 | prefs = await SharedPreferences.getInstance();
20 | await _fillEmptyStorage();
21 | }
22 |
23 | Future ensurePrefsInitialized() async {
24 | if (prefs == null) {
25 | await _initPrefs();
26 | }
27 | }
28 |
29 | Future _fillEmptyStorage() async{
30 | List defaultCities = ["Paris", "New York", "Sydney"];
31 | List defaultColors = ["4294959426", "4282566399", "4294927572"];
32 |
33 | List cities = getCities();
34 | List colors = getColors();
35 |
36 | if(cities.length == 0 || colors.length == 0){
37 | await prefs!.setStringList('cities', defaultCities);
38 | await prefs!.setStringList('colors', defaultColors);
39 | }
40 | }
41 |
42 | @override
43 | Future saveCity(String cityName, String colorValue) async {
44 | final List cities = getCities();
45 | final List colors = getColors();
46 |
47 | if(!cities.contains(cityName)){
48 | cities.add(cityName);
49 | colors.add(colorValue);
50 | await prefs!.setStringList('cities', cities);
51 | await prefs!.setStringList('colors', colors);
52 |
53 | return true;
54 | }
55 |
56 | return false;
57 | }
58 |
59 | @override
60 | List getCities() {
61 | final List cities = prefs!.getStringList('cities') ?? [];
62 |
63 | return cities;
64 | }
65 |
66 | List getColors(){
67 | final List colors = prefs!.getStringList('colors') ?? [];
68 |
69 | return colors;
70 | }
71 |
72 | @override
73 | Future deleteCity(String cityName) async {
74 | List cities = getCities();
75 | List colors = getColors();
76 | int colorIndex = cities.indexWhere((element) => element == cityName);
77 |
78 | cities.remove(cityName);
79 | colors.removeAt(colorIndex);
80 |
81 | await prefs!.remove('cities');
82 | await prefs!.remove('colors');
83 | await prefs!.setStringList('cities', cities);
84 | await prefs!.setStringList('colors', colors);
85 | }
86 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/date/date.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/date/date_card.dart';
4 |
5 | class Date extends StatefulWidget {
6 | final String date;
7 | final Color color;
8 |
9 | const Date({
10 | required this.date,
11 | required this.color,
12 | super.key
13 | });
14 |
15 | @override
16 | State createState() => _DateState();
17 | }
18 |
19 | class _DateState extends State {
20 | final Duration animationDuration = const Duration(milliseconds: 500);
21 | final Curve animationCurve = Curves.easeOutSine;
22 | bool isTextAnimated = false;
23 | bool isContainerAnimated = false;
24 |
25 | @override
26 | void initState() {
27 | _startContainerAnimation();
28 | _startTextAnimation();
29 | super.initState();
30 | }
31 |
32 | void _startTextAnimation() async {
33 | await Future.delayed(const Duration(milliseconds: 500))
34 | .then((value) => setState(() => isTextAnimated = true));
35 | }
36 |
37 | void _startContainerAnimation() async {
38 | await Future.delayed(const Duration(seconds: 0))
39 | .then((value) => setState(() => isContainerAnimated = true));
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Center(
45 | child: Stack(
46 | children: [
47 | DateCard(
48 | backgroundColor: Colors.transparent,
49 | textColor: Colors.transparent,
50 | text: widget.date,
51 | ),
52 |
53 | TopAnimation(
54 | curve: animationCurve,
55 | duration: animationDuration,
56 | animationField: isContainerAnimated,
57 | positionInitialValue: MediaQuery.of(context).size.height/40,
58 | opacityInitialValue: 0,
59 | child: DateCard(
60 | backgroundColor: Colors.black,
61 | textColor: Colors.transparent,
62 | text: widget.date,
63 | ),
64 | ),
65 |
66 | TopAnimation(
67 | curve: animationCurve,
68 | duration: animationDuration,
69 | animationField: isTextAnimated,
70 | positionInitialValue: 20,
71 | opacityInitialValue: 0,
72 | child: Padding(
73 | padding: const EdgeInsets.symmetric(
74 | horizontal: 15,
75 | vertical: 5
76 | ),
77 | child: Text(
78 | widget.date,
79 | style: TextStyle(
80 | color: widget.color,
81 | ),
82 | ),
83 | )
84 | )
85 | ],
86 | ),
87 | );
88 | }
89 | }
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | def keystoreProperties = new Properties()
29 | def keystorePropertiesFile = rootProject.file('key.properties')
30 | if (keystorePropertiesFile.exists()) {
31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
32 | }
33 |
34 | android {
35 | namespace "com.tortamque.weathque"
36 | compileSdkVersion flutter.compileSdkVersion
37 | ndkVersion flutter.ndkVersion
38 |
39 | dependenciesInfo {
40 | includeInApk = false
41 | includeInBundle = false
42 | }
43 |
44 | compileOptions {
45 | sourceCompatibility JavaVersion.VERSION_1_8
46 | targetCompatibility JavaVersion.VERSION_1_8
47 | }
48 |
49 | kotlinOptions {
50 | jvmTarget = '1.8'
51 | }
52 |
53 | sourceSets {
54 | main.java.srcDirs += 'src/main/kotlin'
55 | }
56 |
57 | defaultConfig {
58 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
59 | applicationId "com.tortamque.weathque"
60 | // You can update the following values to match your application needs.
61 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
62 | minSdkVersion flutter.minSdkVersion
63 | targetSdkVersion flutter.targetSdkVersion
64 | versionCode flutterVersionCode.toInteger()
65 | versionName flutterVersionName
66 | }
67 |
68 | signingConfigs {
69 | release {
70 | keyAlias keystoreProperties['keyAlias']
71 | keyPassword keystoreProperties['keyPassword']
72 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
73 | storePassword keystoreProperties['storePassword']
74 | }
75 | }
76 | buildTypes {
77 | release {
78 | signingConfig signingConfigs.release
79 | }
80 | }
81 |
82 | }
83 |
84 | flutter {
85 | source '../..'
86 | }
87 |
88 | dependencies {
89 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
90 | }
91 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/bottom_sheet/widgets/city_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:weathque/core/dependency_injection.dart';
4 | import 'package:weathque/features/app/domain/usecases/delete_city.dart';
5 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart';
6 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
7 |
8 | class CityCard extends StatefulWidget {
9 | final String name;
10 | final int index;
11 | final bool isLast;
12 |
13 | const CityCard({super.key, required this.name, required this.index, required this.isLast});
14 |
15 | @override
16 | State createState() => _CityCardState();
17 | }
18 |
19 | class _CityCardState extends State {
20 | bool isAnimated = false;
21 | final Duration animationDuration = const Duration(milliseconds: 750);
22 | final Curve animationCurve = Curves.easeOutSine;
23 | final int animationStep = 200;
24 |
25 | @override
26 | void initState() {
27 | _animate();
28 | super.initState();
29 | }
30 |
31 | void _animate() async{
32 | await Future.delayed(Duration(milliseconds: (widget.index + 1) * animationStep))
33 | .then((value) => setState(() => isAnimated = true));
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return Stack(
39 | children: [
40 | SizedBox(
41 | child: Center(
42 | child: Padding(
43 | padding: const EdgeInsets.symmetric(vertical: 20),
44 | child: Text(
45 | widget.name,
46 | style: TextStyle(
47 | fontSize: 24,
48 | color: Colors.transparent
49 | ),
50 | )
51 | )
52 | ),
53 | ),
54 |
55 | TopAnimation(
56 | curve: animationCurve,
57 | duration: animationDuration,
58 | animationField: isAnimated,
59 | positionInitialValue: MediaQuery.of(context).size.height/11,
60 | opacityInitialValue: 1,
61 | child: Padding(
62 | padding: const EdgeInsets.symmetric(vertical: 2.5),
63 | child: Dismissible(
64 | key: Key(widget.name),
65 | confirmDismiss: (direction) async {
66 | return !widget.isLast;
67 | },
68 | onDismissed: (_) async {
69 | await locator()(widget.name);
70 | context.read()();
71 | },
72 | child: Card(
73 | shape: RoundedRectangleBorder(
74 | borderRadius: BorderRadius.all(Radius.circular(15.0))
75 | ),
76 | child: Center(
77 | child: Padding(
78 | padding: const EdgeInsets.symmetric(vertical: 15),
79 | child: Text(
80 | widget.name,
81 | style: TextStyle(
82 | fontSize: 24,
83 | ),
84 | )
85 | )
86 | ),
87 | ),
88 | ),
89 | ),
90 | ),
91 | ],
92 | );
93 | }
94 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/temperature/temperature.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/right_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
4 | import 'package:weathque/features/app/presentation/widgets/temperature/temperature_text.dart';
5 |
6 | class Temperature extends StatefulWidget {
7 | final String temperature;
8 |
9 | const Temperature({
10 | required this.temperature,
11 | super.key
12 | });
13 |
14 | @override
15 | State createState() => _TemperatureState();
16 | }
17 |
18 | class _TemperatureState extends State {
19 | final Duration temperatureAnimationDuration = const Duration(milliseconds: 500);
20 | final Curve temperatureAnimationCurve = Curves.easeOutSine;
21 | final Duration degreeAnimationDuration = const Duration(milliseconds: 700);
22 | final Curve degreeAnimationCurve = Curves.easeOutCubic;
23 | bool isAnimatedTemperature = false;
24 | bool isAnimatedDegree = false;
25 |
26 | @override
27 | void initState() {
28 | _startAnimationTemperature();
29 | _startAnimationDegree();
30 | super.initState();
31 | }
32 |
33 | void _startAnimationTemperature() async{
34 | await Future.delayed(const Duration(milliseconds: 1000))
35 | .then((value) => setState(() => isAnimatedTemperature = true));
36 | }
37 |
38 | void _startAnimationDegree() async{
39 | await Future.delayed(const Duration(milliseconds: 1400))
40 | .then((value) => setState(() => isAnimatedDegree = true));
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Expanded(
46 | flex: 1,
47 | child: Center(
48 | child: FractionallySizedBox(
49 | heightFactor: 0.9,
50 | widthFactor: 0.9,
51 | child: FittedBox(
52 | fit: BoxFit.contain,
53 | child: Row(
54 | children: [
55 | Stack(
56 | children: [
57 | TemperatureText(
58 | text: "${widget.temperature}°",
59 | color: Colors.transparent
60 | ),
61 |
62 | TopAnimation(
63 | animationField: isAnimatedTemperature,
64 | duration: temperatureAnimationDuration,
65 | curve: temperatureAnimationCurve,
66 | positionInitialValue: MediaQuery.of(context).size.height/500,
67 | opacityInitialValue: 0,
68 | child: TemperatureText(
69 | text: widget.temperature,
70 | color: Colors.black
71 | ),
72 | ),
73 |
74 | RightAnimation(
75 | animationField: isAnimatedDegree,
76 | duration: degreeAnimationDuration,
77 | curve: degreeAnimationCurve,
78 | positionInitialValue: MediaQuery.of(context).size.width/55,
79 | opacityInitialValue: 0,
80 | child: const TemperatureText(
81 | text: "°",
82 | color: Colors.black
83 | ),
84 | )
85 | ],
86 | ),
87 | ],
88 | )
89 | ),
90 | ),
91 | ),
92 | );
93 | }
94 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/menu/menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:weathque/core/dependency_injection.dart';
4 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart';
5 | import 'package:weathque/features/app/domain/entities/summary_builder.dart';
6 | import 'package:weathque/features/app/domain/entities/weather_entity.dart';
7 | import 'package:weathque/features/app/presentation/widgets/condition/condition.dart';
8 | import 'package:weathque/features/app/presentation/widgets/date/date.dart';
9 | import 'package:weathque/features/app/presentation/widgets/misc/headers/summary_header/header.dart';
10 | import 'package:weathque/features/app/presentation/widgets/misc/headers/forecast_header/weekly_forecast_header.dart';
11 | import 'package:weathque/features/app/presentation/widgets/summary_text/summary.dart';
12 | import 'package:weathque/features/app/presentation/widgets/temperature/temperature.dart';
13 | import 'package:weathque/features/app/presentation/widgets/weather_card/weather_card.dart';
14 | import 'package:weathque/features/app/presentation/widgets/weekly_forecast/weekly_forecast.dart';
15 |
16 |
17 |
18 | class Menu extends StatelessWidget {
19 | final WeatherEntity? weatherEntity;
20 | final ForecastWeatherEntity? forecastWeatherEntity;
21 | final String currentDate = DateFormat('EEEE, d MMMM').format(DateTime.now());
22 | final Color color;
23 | late final String condition;
24 | late final String temperature;
25 | late final String summary;
26 | late final String windSpeed;
27 | late final String humidity;
28 | late final String visibility;
29 |
30 | Menu({
31 | required this.weatherEntity,
32 | required this.forecastWeatherEntity,
33 | required this.color,
34 | super.key
35 | }){
36 | condition = weatherEntity != null ?
37 | weatherEntity!.weather[0].main
38 | : "Not available";
39 |
40 | temperature = weatherEntity != null ?
41 | weatherEntity!.information.temp!.round().toString()
42 | : "0";
43 |
44 | summary = weatherEntity != null ?
45 | locator()(weatherEntity!)
46 | : "Not available";
47 |
48 | windSpeed = weatherEntity != null ?
49 | weatherEntity!.wind.speed.toString()
50 | : "Not available";
51 |
52 | humidity = weatherEntity != null ?
53 | weatherEntity!.information.humidity.toString()
54 | : "Not available";
55 |
56 | visibility = weatherEntity != null ?
57 | (weatherEntity!.visibility/1000).round().toString()
58 | : "Not available";
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return Expanded(
64 | flex: 11,
65 | child: Column(
66 | mainAxisAlignment: MainAxisAlignment.spaceAround,
67 | crossAxisAlignment: CrossAxisAlignment.start,
68 | children: [
69 | Date(date: currentDate, color: color),
70 | Condition(condition: condition),
71 | Temperature(temperature: temperature),
72 | const Header(text: "Daily Summary"),
73 | Summary(text: summary),
74 | const SizedBox(height: 25),
75 | WeatherCard(
76 | humidity: humidity,
77 | visibility: visibility,
78 | windSpeed: windSpeed,
79 | color: color,
80 | ),
81 | const SizedBox(height: 25),
82 | const WeeklyForecastHeader(),
83 | WeeklyForecast(
84 | forecastWeatherEntity: forecastWeatherEntity,
85 | )
86 | ],
87 | ),
88 | );
89 | }
90 | }
--------------------------------------------------------------------------------
/lib/core/dependency_injection.dart:
--------------------------------------------------------------------------------
1 | import 'package:get_it/get_it.dart';
2 | // ignore: depend_on_referenced_packages
3 | import 'package:dio/dio.dart';
4 | import 'package:weathque/features/app/data/data_sources/local/storage.dart';
5 | import 'package:weathque/features/app/data/data_sources/remote/api_service.dart';
6 | import 'package:weathque/features/app/data/repository/storage_repository_implementation.dart';
7 | import 'package:weathque/features/app/data/repository/weather_repository_implementation.dart';
8 | import 'package:weathque/features/app/domain/entities/summary_builder.dart';
9 | import 'package:weathque/features/app/domain/repository/weather_repository.dart';
10 | import 'package:weathque/features/app/domain/usecases/delete_city.dart';
11 | import 'package:weathque/features/app/domain/usecases/get_cities.dart';
12 | import 'package:weathque/features/app/domain/usecases/get_colors.dart';
13 | import 'package:weathque/features/app/domain/usecases/get_current_weather.dart';
14 | import 'package:weathque/features/app/domain/usecases/get_weather_forecast.dart';
15 | import 'package:weathque/features/app/domain/usecases/save_city.dart';
16 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart';
17 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_bloc.dart';
18 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_bloc.dart';
19 |
20 | GetIt locator = GetIt.instance;
21 |
22 | Future initializeDependencies() async {
23 | // Dio
24 | locator.registerSingleton(Dio());
25 |
26 | // API Service
27 | locator.registerSingleton(
28 | CurrentWeatherApiService(locator())
29 | );
30 | locator.registerSingleton(
31 | ForecastWeatherApiService(locator())
32 | );
33 |
34 | // Storage
35 | locator.registerSingleton(StorageImplementation());
36 |
37 | // Repo
38 | locator.registerSingleton(
39 | WeatherRepositoryImplementation(locator())
40 | );
41 | locator.registerSingleton(
42 | ForecastWeatherRepositoryImplementation(locator())
43 | );
44 | locator.registerSingleton(
45 | StorageRepositoryImplementation(locator())
46 | );
47 |
48 | // Use cases
49 | locator.registerSingleton(
50 | GetCurrentWeatherUseCase(locator())
51 | );
52 | locator.registerSingleton(
53 | GetWeatherForecastUseCase(locator())
54 | );
55 | locator.registerSingleton(
56 | SaveCityUseCaseImplementation(locator())
57 | );
58 | locator.registerSingleton(
59 | GetCitiesUseCaseImplementation(locator())
60 | );
61 | locator.registerSingleton(
62 | DeleteCityUseCaseImplementation(locator())
63 | );
64 | locator.registerSingleton(
65 | GetColorsUseCaseImplementation(locator())
66 | );
67 |
68 | // Blocs
69 | locator.registerFactory(
70 | () => GetCurrentWeatherBloc(locator())
71 | );
72 | locator.registerFactory(
73 | () => GetWeatherForecastBloc(locator())
74 | );
75 |
76 | // Cubit
77 | locator.registerFactory(
78 | () => CitiesChangedCubit()
79 | );
80 |
81 | // Entities
82 | locator.registerSingleton(SummaryBuilder());
83 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/summary_text/summary.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/summary_text/summary_text.dart';
4 |
5 | // ignore: must_be_immutable
6 | class Summary extends StatefulWidget {
7 | final String text;
8 | late String _line1;
9 | late String _line2;
10 | late String _line3;
11 |
12 | Summary({
13 | required this.text,
14 | super.key
15 | }){
16 | _line1 = text.split("\n")[0];
17 | _line2 = text.split("\n")[1];
18 | _line3 = text.split("\n")[2];
19 | }
20 |
21 | @override
22 | State createState() => _SummaryState();
23 | }
24 |
25 | class _SummaryState extends State {
26 | final Duration animationDuration = const Duration(milliseconds: 500);
27 | final Curve animationCurve = Curves.easeOutSine;
28 | bool isAnimatedLine1 = false;
29 | bool isAnimatedLine2 = false;
30 | bool isAnimatedLine3 = false;
31 |
32 | @override
33 | void initState() {
34 | _startAnimationLine1();
35 | _startAnimationLine2();
36 | _startAnimationLine3();
37 | super.initState();
38 | }
39 |
40 | void _startAnimationLine1() async{
41 | await Future.delayed(const Duration(milliseconds: 1400))
42 | .then((value) => setState(() => isAnimatedLine1 = true));
43 | }
44 |
45 | void _startAnimationLine2() async{
46 | await Future.delayed(const Duration(milliseconds: 1600))
47 | .then((value) => setState(() => isAnimatedLine2 = true));
48 | }
49 |
50 | void _startAnimationLine3() async{
51 | await Future.delayed(const Duration(milliseconds: 1800))
52 | .then((value) => setState(() => isAnimatedLine3 = true));
53 | }
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | return FittedBox(
58 | fit: BoxFit.contain,
59 | child: Column(
60 | crossAxisAlignment: CrossAxisAlignment.start,
61 | children: [
62 | Stack(
63 | children: [
64 | SummaryText(text: widget._line1, color: Colors.transparent),
65 | TopAnimation(
66 | curve: animationCurve,
67 | duration: animationDuration,
68 | animationField: isAnimatedLine1,
69 | positionInitialValue: MediaQuery.of(context).size.height/40,
70 | opacityInitialValue: 0,
71 | child: SummaryText(text: widget._line1, color: Colors.black)
72 | )
73 | ],
74 | ),
75 | Stack(
76 | children: [
77 | SummaryText(text: widget._line2, color: Colors.transparent),
78 | TopAnimation(
79 | curve: animationCurve,
80 | duration: animationDuration,
81 | animationField: isAnimatedLine2,
82 | positionInitialValue: MediaQuery.of(context).size.height/40,
83 | opacityInitialValue: 0,
84 | child: SummaryText(text: widget._line2, color: Colors.black)
85 | )
86 | ],
87 | ),
88 | Stack(
89 | children: [
90 | SummaryText(text: widget._line3, color: Colors.transparent),
91 | TopAnimation(
92 | curve: animationCurve,
93 | duration: animationDuration,
94 | animationField: isAnimatedLine3,
95 | positionInitialValue: MediaQuery.of(context).size.height/40,
96 | opacityInitialValue: 0,
97 | child: SummaryText(text: widget._line3, color: Colors.black)
98 | )
99 | ],
100 | ),
101 | ],
102 | ),
103 | );
104 | }
105 | }
--------------------------------------------------------------------------------
/lib/features/app/presentation/widgets/misc/headers/forecast_header/weekly_forecast_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:weathque/features/app/presentation/widgets/animations/right_animation.dart';
3 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart';
4 | import 'package:weathque/features/app/presentation/widgets/misc/headers/summary_header/header_text.dart';
5 |
6 | class WeeklyForecastHeader extends StatefulWidget {
7 | const WeeklyForecastHeader({super.key});
8 |
9 | @override
10 | State createState() => _WeeklyForecastHeaderState();
11 | }
12 |
13 | class _WeeklyForecastHeaderState extends State {
14 | final Duration animationDurationText = const Duration(milliseconds: 500);
15 | final Duration animationDurationArrow = const Duration(milliseconds: 1000);
16 | final Curve animationCurve = Curves.easeOutSine;
17 | bool isAnimatedWeekly = false;
18 | bool isAnimatedForecast = false;
19 | bool isAnimatedArrow = false;
20 |
21 | @override
22 | void initState() {
23 | _startAnimationWeekly();
24 | _startAnimationForecast();
25 | _startAnimationArrow();
26 | super.initState();
27 | }
28 |
29 | void _startAnimationWeekly() async{
30 | await Future.delayed(const Duration(milliseconds: 2200))
31 | .then((value) => setState(() => isAnimatedWeekly = true));
32 | }
33 |
34 | void _startAnimationForecast() async{
35 | await Future.delayed(const Duration(milliseconds: 2400))
36 | .then((value) => setState(() => isAnimatedForecast = true));
37 | }
38 |
39 | void _startAnimationArrow() async{
40 | await Future.delayed(const Duration(milliseconds: 2500))
41 | .then((value) => setState(() => isAnimatedArrow = true));
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | return Stack(
47 | alignment: Alignment.centerRight,
48 | children: [
49 | Row(
50 | children: [
51 | Stack(
52 | children: [
53 | const HeaderText(text: "Weekly", color: Colors.transparent),
54 | TopAnimation(
55 | curve: animationCurve,
56 | duration: animationDurationText,
57 | animationField: isAnimatedWeekly,
58 | positionInitialValue: MediaQuery.of(context).size.height/40,
59 | opacityInitialValue: 0,
60 | child: const HeaderText(text: "Weekly", color: Colors.black)
61 | )
62 | ],
63 | ),
64 |
65 | Stack(
66 | children: [
67 | const HeaderText(text: " forecast", color: Colors.transparent),
68 | TopAnimation(
69 | curve: animationCurve,
70 | duration: animationDurationText,
71 | animationField: isAnimatedForecast,
72 | positionInitialValue: MediaQuery.of(context).size.height/40,
73 | opacityInitialValue: 0,
74 | child: const HeaderText(text: " forecast", color: Colors.black),
75 | )
76 | ],
77 | ),
78 | ]
79 | ),
80 | Stack(
81 | alignment: Alignment.centerRight,
82 | children: [
83 | const SizedBox(
84 | height: 17.5,
85 | width: double.infinity,
86 | ),
87 |
88 | RightAnimation(
89 | curve: animationCurve,
90 | duration: animationDurationArrow,
91 | animationField: isAnimatedArrow,
92 | positionInitialValue: MediaQuery.of(context).size.width/3.75,
93 | opacityInitialValue: 0,
94 | child: Image.asset("assets/icons/arrow.png", height: 17.5),
95 | )
96 | ],
97 | )
98 | ],
99 | );
100 | }
101 | }
--------------------------------------------------------------------------------
/lib/features/app/domain/entities/weather_entity.dart:
--------------------------------------------------------------------------------
1 | class WeatherEntity {
2 | final Coord coord;
3 | final List weather;
4 | final String base;
5 | final Information information;
6 | final int visibility;
7 | final Wind wind;
8 | final Clouds clouds;
9 | final int dt;
10 | final SunInformation sunInformation;
11 | final int timezone;
12 | final int id;
13 | final String name;
14 | final int cod;
15 |
16 | WeatherEntity({
17 | required this.coord,
18 | required this.weather,
19 | required this.base,
20 | required this.information,
21 | required this.visibility,
22 | required this.wind,
23 | required this.clouds,
24 | required this.dt,
25 | required this.sunInformation,
26 | required this.timezone,
27 | required this.id,
28 | required this.name,
29 | required this.cod,
30 | });
31 | }
32 |
33 | class Coord {
34 | final double lon;
35 | final double lat;
36 |
37 | Coord({required this.lon, required this.lat});
38 |
39 | factory Coord.fromJson(Map json) {
40 | return Coord(
41 | lon: json['lon'].toDouble(),
42 | lat: json['lat'].toDouble(),
43 | );
44 | }
45 | }
46 |
47 | class WeatherData {
48 | final int id;
49 | final String main;
50 | final String description;
51 | final String icon;
52 |
53 | WeatherData({
54 | required this.id,
55 | required this.main,
56 | required this.description,
57 | required this.icon,
58 | });
59 |
60 | factory WeatherData.fromJson(Map json) {
61 | return WeatherData(
62 | id: json['id'],
63 | main: json['main'],
64 | description: json['description'],
65 | icon: json['icon'],
66 | );
67 | }
68 | }
69 |
70 | class Information {
71 | final double? temp;
72 | final double? feelsLike;
73 | final double? tempMin;
74 | final double? tempMax;
75 | final int? pressure;
76 | final int? humidity;
77 | final int? seaLevel;
78 | final int? groundLevel;
79 |
80 | Information({
81 | required this.temp,
82 | required this.feelsLike,
83 | required this.tempMin,
84 | required this.tempMax,
85 | required this.pressure,
86 | required this.humidity,
87 | required this.seaLevel,
88 | required this.groundLevel,
89 | });
90 |
91 | factory Information.fromJson(Map json) {
92 | return Information(
93 | temp: json['temp'].toDouble(),
94 | feelsLike: json['feels_like'].toDouble(),
95 | tempMin: json['temp_min'].toDouble(),
96 | tempMax: json['temp_max'].toDouble(),
97 | pressure: json['pressure'],
98 | humidity: json['humidity'],
99 | seaLevel: json['sea_level'],
100 | groundLevel: json['grnd_level'],
101 | );
102 | }
103 | }
104 |
105 | class Wind {
106 | final double? speed;
107 | final int? deg;
108 | final double? gust;
109 |
110 | Wind({
111 | required this.speed,
112 | required this.deg,
113 | required this.gust,
114 | });
115 |
116 | factory Wind.fromJson(Map json) {
117 | return Wind(
118 | speed: json['speed']?.toDouble(),
119 | deg: json['deg'],
120 | gust: json['gust']?.toDouble(),
121 | );
122 | }
123 | }
124 |
125 | class Clouds {
126 | final int all;
127 |
128 | Clouds({required this.all});
129 |
130 | factory Clouds.fromJson(Map json) {
131 | return Clouds(
132 | all: json['all'],
133 | );
134 | }
135 | }
136 |
137 | class SunInformation {
138 | final String country;
139 | final int sunrise;
140 | final int sunset;
141 |
142 | SunInformation({
143 | required this.country,
144 | required this.sunrise,
145 | required this.sunset,
146 | });
147 |
148 | factory SunInformation.fromJson(Map json) {
149 | return SunInformation(
150 | country: json['country'],
151 | sunrise: json['sunrise'],
152 | sunset: json['sunset'],
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/lib/features/app/data/data_sources/remote/api_service.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'api_service.dart';
4 |
5 | // **************************************************************************
6 | // RetrofitGenerator
7 | // **************************************************************************
8 |
9 | // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers
10 |
11 | class _CurrentWeatherApiService implements CurrentWeatherApiService {
12 | _CurrentWeatherApiService(
13 | this._dio, {
14 | this.baseUrl,
15 | }) {
16 | baseUrl ??= 'https://api.openweathermap.org/data/2.5/weather';
17 | }
18 |
19 | final Dio _dio;
20 |
21 | String? baseUrl;
22 |
23 | @override
24 | Future> getCurrentWeather({
25 | required cityName,
26 | required apiKey,
27 | units = "metric",
28 | }) async {
29 | const _extra = {};
30 | final queryParameters = {
31 | r'q': cityName,
32 | r'appid': apiKey,
33 | r'units': units,
34 | };
35 | final _headers = {};
36 | final Map? _data = null;
37 | final _result = await _dio.fetch