├── 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>( 38 | _setStreamType>(Options( 39 | method: 'GET', 40 | headers: _headers, 41 | extra: _extra, 42 | ) 43 | .compose( 44 | _dio.options, 45 | '', 46 | queryParameters: queryParameters, 47 | data: _data, 48 | ) 49 | .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl))); 50 | final value = WeatherModel.fromJson(_result.data!); 51 | final httpResponse = HttpResponse(value, _result); 52 | return httpResponse; 53 | } 54 | 55 | RequestOptions _setStreamType(RequestOptions requestOptions) { 56 | if (T != dynamic && 57 | !(requestOptions.responseType == ResponseType.bytes || 58 | requestOptions.responseType == ResponseType.stream)) { 59 | if (T == String) { 60 | requestOptions.responseType = ResponseType.plain; 61 | } else { 62 | requestOptions.responseType = ResponseType.json; 63 | } 64 | } 65 | return requestOptions; 66 | } 67 | } 68 | 69 | class _ForecastWeatherApiService implements ForecastWeatherApiService { 70 | _ForecastWeatherApiService( 71 | this._dio, { 72 | this.baseUrl, 73 | }) { 74 | baseUrl ??= 'https://api.openweathermap.org/data/2.5/forecast'; 75 | } 76 | 77 | final Dio _dio; 78 | 79 | String? baseUrl; 80 | 81 | @override 82 | Future> getForecast({ 83 | required cityName, 84 | required apiKey, 85 | units = "metric", 86 | }) async { 87 | const _extra = {}; 88 | final queryParameters = { 89 | r'q': cityName, 90 | r'appid': apiKey, 91 | r'units': units, 92 | }; 93 | final _headers = {}; 94 | final Map? _data = null; 95 | final _result = await _dio.fetch>( 96 | _setStreamType>(Options( 97 | method: 'GET', 98 | headers: _headers, 99 | extra: _extra, 100 | ) 101 | .compose( 102 | _dio.options, 103 | '', 104 | queryParameters: queryParameters, 105 | data: _data, 106 | ) 107 | .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl))); 108 | final value = ForecastWeatherModel.fromJson(_result.data!); 109 | final httpResponse = HttpResponse(value, _result); 110 | return httpResponse; 111 | } 112 | 113 | RequestOptions _setStreamType(RequestOptions requestOptions) { 114 | if (T != dynamic && 115 | !(requestOptions.responseType == ResponseType.bytes || 116 | requestOptions.responseType == ResponseType.stream)) { 117 | if (T == String) { 118 | requestOptions.responseType = ResponseType.plain; 119 | } else { 120 | requestOptions.responseType = ResponseType.json; 121 | } 122 | } 123 | return requestOptions; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/features/app/presentation/widgets/misc/bottom_sheet/bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:weathque/config/theme/custom_colors.dart'; 6 | import 'package:weathque/core/dependency_injection.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:weathque/features/app/domain/usecases/get_current_weather.dart'; 9 | import 'package:weathque/features/app/domain/usecases/save_city.dart'; 10 | import 'package:weathque/features/app/presentation/bloc/add_city/cities_changed_cubit.dart'; 11 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_bloc.dart'; 12 | import 'package:weathque/features/app/presentation/bloc/get_current_weather/get_current_weather_event.dart'; 13 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_bloc.dart'; 14 | import 'package:weathque/features/app/presentation/bloc/get_weather_forecast/get_weather_forecast_event.dart'; 15 | import 'package:weathque/features/app/presentation/widgets/misc/bottom_sheet/widgets/bottom_sheet_list_view.dart'; 16 | import 'package:weathque/features/app/presentation/widgets/misc/bottom_sheet/widgets/bottom_sheet_text_field.dart'; 17 | import 'package:weathque/features/app/presentation/widgets/misc/toast/custom_toast.dart'; 18 | 19 | TextEditingController _cityController = TextEditingController(); 20 | 21 | showCustomBottomSheet(BuildContext context, Color backgroundColor){ 22 | showModalBottomSheet( 23 | context: context, 24 | isScrollControlled: true, 25 | shape: RoundedRectangleBorder( 26 | borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)), 27 | ), 28 | backgroundColor: backgroundColor, 29 | builder:(context) { 30 | return _buildBottomSheetMenu(context); 31 | }, 32 | ); 33 | } 34 | 35 | Widget _buildBottomSheetMenu(BuildContext context){ 36 | FToast toastManager = FToast(); 37 | toastManager.init(context); 38 | 39 | return Container( 40 | width: double.infinity, 41 | height: MediaQuery.of(context).size.height - (kBottomNavigationBarHeight + kToolbarHeight), 42 | child: Padding( 43 | padding: EdgeInsets.symmetric(vertical: 30, horizontal: 15), 44 | child: Column( 45 | crossAxisAlignment: CrossAxisAlignment.center, 46 | children: [ 47 | BottomSheetTextField( 48 | controller: _cityController, 49 | toastManager: toastManager, 50 | onSubmit: _onSubmit 51 | ), 52 | 53 | BottomSheetListView() 54 | ], 55 | ), 56 | ), 57 | ); 58 | } 59 | 60 | void _onSubmit(FToast toastManager, BuildContext context) async { 61 | String cityName = _cityController.text; 62 | 63 | try { 64 | await locator()(cityName: cityName); 65 | } catch (_) { 66 | _onError(toastManager); 67 | return; 68 | } 69 | _onSuccess(toastManager, context); 70 | } 71 | 72 | void _onError(FToast toastManager){ 73 | CustomToast toast = CustomToast( 74 | isError: true, 75 | text: "This city cannot be found in the database", 76 | ); 77 | toastManager.showToast( 78 | child: toast, 79 | gravity: ToastGravity.BOTTOM, 80 | toastDuration: Duration(seconds: 2), 81 | ); 82 | } 83 | 84 | Future _onSuccess(FToast toastManager, BuildContext context) async { 85 | int randomIndex = Random().nextInt(CustomColors.values.length); 86 | Color randomColor = CustomColors.values[randomIndex].color; 87 | 88 | bool preservationResult = await locator()(cityName: _cityController.text, colorValue: randomColor.value.toString()); 89 | late CustomToast toast; 90 | 91 | if(preservationResult){ 92 | toast = CustomToast( 93 | isError: false, 94 | text: "City was successfully saved", 95 | ); 96 | } else{ 97 | toast = CustomToast( 98 | isError: true, 99 | text: "This city is already saved in the database", 100 | ); 101 | } 102 | toastManager.showToast( 103 | child: toast, 104 | gravity: ToastGravity.BOTTOM, 105 | toastDuration: Duration(seconds: 2), 106 | ); 107 | BlocProvider.of(context).add(const GetCurrentWeather()); 108 | BlocProvider.of(context).add(const GetWeatherForecast()); 109 | context.read()(); 110 | _cityController.text = ""; 111 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: weathque 2 | description: A new Flutter project. 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.1.0+2 20 | 21 | environment: 22 | sdk: '>=3.0.0 <4.0.0' 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | 35 | # The following adds the Cupertino Icons font to your application. 36 | # Use with the CupertinoIcons class for iOS styleF icons. 37 | cupertino_icons: ^1.0.2 38 | retrofit: '>=4.0.0 <5.0.0' 39 | get_it: ^7.6.0 40 | flutter_bloc: ^8.1.3 41 | intl: ^0.18.1 42 | flutter_carousel_slider: ^1.1.0 43 | fluttertoast: ^8.2.2 44 | shared_preferences: ^2.2.0 45 | 46 | dev_dependencies: 47 | flutter_test: 48 | sdk: flutter 49 | retrofit_generator: '>=5.0.0 <6.0.0' 50 | build_runner: '>=2.3.0 <4.0.0' 51 | json_serializable: ^6.7.1 52 | flutter_launcher_icons: ^0.13.1 53 | change_app_package_name: ^1.1.0 54 | 55 | flutter_icons: 56 | android: true 57 | ios: true 58 | image_path: assets/icons/app_icon/icon.png 59 | 60 | # The "flutter_lints" package below contains a set of recommended lints to 61 | # encourage good coding practices. The lint set provided by the package is 62 | # activated in the `analysis_options.yaml` file located at the root of your 63 | # package. See that file for information about deactivating specific lint 64 | # rules and activating additional ones. 65 | flutter_lints: ^2.0.0 66 | 67 | # For information on the generic Dart part of this file, see the 68 | # following page: https://dart.dev/tools/pub/pubspec 69 | 70 | # The following section is specific to Flutter packages. 71 | flutter: 72 | 73 | # The following line ensures that the Material Icons font is 74 | # included with your application, so that you can use the icons in 75 | # the material Icons class. 76 | uses-material-design: true 77 | 78 | # To add assets to your application, add an assets section, like this: 79 | assets: 80 | - assets/icons/drop.png 81 | - assets/icons/eye.png 82 | - assets/icons/wind.png 83 | - assets/icons/arrow.png 84 | - assets/icons/weather/cloud.png 85 | - assets/icons/weather/lightning.png 86 | - assets/icons/weather/mist.png 87 | - assets/icons/weather/partly-cloudy.png 88 | - assets/icons/weather/rain.png 89 | - assets/icons/weather/snow.png 90 | - assets/icons/weather/sun.png 91 | - assets/icons/weather/unknown.png 92 | 93 | # An image asset can refer to one or more resolution-specific "variants", see 94 | # https://flutter.dev/assets-and-images/#resolution-aware 95 | 96 | # For details regarding adding assets from package dependencies, see 97 | # https://flutter.dev/assets-and-images/#from-packages 98 | 99 | # To add custom fonts to your application, add a fonts section here, 100 | # in this "flutter" section. Each entry in this list should have a 101 | # "family" key with the font family name, and a "fonts" key with a 102 | # list giving the asset and other descriptors for the font. For 103 | # example: 104 | # fonts: 105 | # - family: Schyler 106 | # fonts: 107 | # - asset: fonts/Schyler-Regular.ttf 108 | # - asset: fonts/Schyler-Italic.ttf 109 | # style: italic 110 | # - family: Trajan Pro 111 | # fonts: 112 | # - asset: fonts/TrajanPro.ttf 113 | # - asset: fonts/TrajanPro_Bold.ttf 114 | # weight: 700 115 | # 116 | # For details regarding fonts from package dependencies, 117 | # see https://flutter.dev/custom-fonts/#from-packages 118 | -------------------------------------------------------------------------------- /lib/features/app/domain/entities/forecast_weather_entity.dart: -------------------------------------------------------------------------------- 1 | class ForecastWeatherEntity { 2 | String cod; 3 | int message; 4 | int cnt; 5 | List list; 6 | ForecastCity city; 7 | 8 | ForecastWeatherEntity({ 9 | required this.cod, 10 | required this.message, 11 | required this.cnt, 12 | required this.list, 13 | required this.city, 14 | }); 15 | } 16 | 17 | class WeatherData { 18 | int dt; 19 | MainData main; 20 | List weather; 21 | Clouds clouds; 22 | Wind wind; 23 | int visibility; 24 | double pop; 25 | Sys sys; 26 | String dtTxt; 27 | 28 | WeatherData({ 29 | required this.dt, 30 | required this.main, 31 | required this.weather, 32 | required this.clouds, 33 | required this.wind, 34 | required this.visibility, 35 | required this.pop, 36 | required this.sys, 37 | required this.dtTxt, 38 | }); 39 | 40 | factory WeatherData.fromJson(Map json) { 41 | List weatherList = (json['weather'] as List) 42 | .map((data) => Weather.fromJson(data)) 43 | .toList(); 44 | 45 | return WeatherData( 46 | dt: json['dt'], 47 | main: MainData.fromJson(json['main']), 48 | weather: weatherList, 49 | clouds: Clouds.fromJson(json['clouds']), 50 | wind: Wind.fromJson(json['wind']), 51 | visibility: json['visibility'], 52 | pop: json['pop'].toDouble(), 53 | sys: Sys.fromJson(json['sys']), 54 | dtTxt: json['dt_txt'], 55 | ); 56 | } 57 | } 58 | 59 | class MainData { 60 | double temp; 61 | double feelsLike; 62 | double tempMin; 63 | double tempMax; 64 | int pressure; 65 | int seaLevel; 66 | int grndLevel; 67 | int humidity; 68 | double tempKf; 69 | 70 | MainData({ 71 | required this.temp, 72 | required this.feelsLike, 73 | required this.tempMin, 74 | required this.tempMax, 75 | required this.pressure, 76 | required this.seaLevel, 77 | required this.grndLevel, 78 | required this.humidity, 79 | required this.tempKf, 80 | }); 81 | 82 | factory MainData.fromJson(Map json) { 83 | return MainData( 84 | temp: json['temp'].toDouble(), 85 | feelsLike: json['feels_like'].toDouble(), 86 | tempMin: json['temp_min'].toDouble(), 87 | tempMax: json['temp_max'].toDouble(), 88 | pressure: json['pressure'], 89 | seaLevel: json['sea_level'], 90 | grndLevel: json['grnd_level'], 91 | humidity: json['humidity'], 92 | tempKf: json['temp_kf'].toDouble(), 93 | ); 94 | } 95 | } 96 | 97 | class Weather { 98 | int id; 99 | String main; 100 | String description; 101 | String icon; 102 | 103 | Weather({ 104 | required this.id, 105 | required this.main, 106 | required this.description, 107 | required this.icon, 108 | }); 109 | 110 | factory Weather.fromJson(Map json) { 111 | return Weather( 112 | id: json['id'], 113 | main: json['main'], 114 | description: json['description'], 115 | icon: json['icon'], 116 | ); 117 | } 118 | } 119 | 120 | class Clouds { 121 | int all; 122 | 123 | Clouds({required this.all}); 124 | 125 | factory Clouds.fromJson(Map json) { 126 | return Clouds(all: json['all']); 127 | } 128 | } 129 | 130 | class Wind { 131 | double speed; 132 | int deg; 133 | double gust; 134 | 135 | Wind({ 136 | required this.speed, 137 | required this.deg, 138 | required this.gust, 139 | }); 140 | 141 | factory Wind.fromJson(Map json) { 142 | return Wind( 143 | speed: json['speed'].toDouble(), 144 | deg: json['deg'], 145 | gust: json['gust'].toDouble(), 146 | ); 147 | } 148 | } 149 | 150 | class Sys { 151 | String pod; 152 | 153 | Sys({required this.pod}); 154 | 155 | factory Sys.fromJson(Map json) { 156 | return Sys(pod: json['pod']); 157 | } 158 | } 159 | 160 | class ForecastCity { 161 | int id; 162 | String name; 163 | Coord coord; 164 | String country; 165 | int population; 166 | int timezone; 167 | int sunrise; 168 | int sunset; 169 | 170 | ForecastCity({ 171 | required this.id, 172 | required this.name, 173 | required this.coord, 174 | required this.country, 175 | required this.population, 176 | required this.timezone, 177 | required this.sunrise, 178 | required this.sunset, 179 | }); 180 | 181 | factory ForecastCity.fromJson(Map json) { 182 | return ForecastCity( 183 | id: json['id'], 184 | name: json['name'], 185 | coord: Coord.fromJson(json['coord']), 186 | country: json['country'], 187 | population: json['population'], 188 | timezone: json['timezone'], 189 | sunrise: json['sunrise'], 190 | sunset: json['sunset'], 191 | ); 192 | } 193 | } 194 | 195 | class Coord { 196 | double lat; 197 | double lon; 198 | 199 | Coord({required this.lat, required this.lon}); 200 | 201 | factory Coord.fromJson(Map json) { 202 | return Coord(lat: json['lat'].toDouble(), lon: json['lon'].toDouble()); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Weathque 3 | 4 | ## Description 5 | Weathque is a dynamic weather application created using Flutter and Dart, offering a seamless way to stay informed about the weather conditions. Embrace every day fully prepared with accurate real-time weather updates and a comprehensive 5-day forecast.
6 | Designed with the user in mind, Weathque integrates a [Clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) pattern, combining [Repository](https://developer.android.com/codelabs/basic-android-kotlin-training-repository-pattern#3) and [Bloc](https://bloclibrary.dev/#/flutterbloccoreconcepts) patterns, to ensure efficient data management and an intuitive experience. 7 | 8 | ## Table of Contents 9 | - [Design](#design) 10 | - [Architecture](#architecture) 11 | - [Features](#features) 12 | - [Supported platforms](#supported-platforms) 13 | - [Installation](#installation) 14 | - [Usage](#usage) 15 | - [Screenshots and Demo](#screenshots-and-demo) 16 | - [Changelog](#changelog) 17 | - [Used Packages](#used-packages) 18 | - [License](#license) 19 | 20 | 21 | ## Design 22 | ![Design](https://github.com/tortamque/Weathque/assets/90132962/e50c68f4-77da-4de9-8da8-c4302d3e8515) 23 |
All design credits belong to [Desire Creative Agency](https://dribbble.com/desire-creative_agency). 24 |
The original design was taken from [Dribbble](https://dribbble.com/shots/20675054-Mobile-Weather-app). 25 | 26 | 27 | ## Architecture 28 | Weathque fully embraces the Clean Architecture pattern, incorporating both the Bloc and Repository patterns.
29 | Clean Architecture consists of the following layers: 30 | 1) Data Layer 31 | 2) Domain Layer (Business Logic) 32 | 3) Presentation Layer (UI)

33 | 34 | Architecture for API calls: 35 | ![API](https://github.com/tortamque/Weathque/assets/90132962/4293e9f4-f2ef-4255-ae59-b6caa9a0bb3f) 36 | 37 | Architecture for Storage calls: 38 | ![Prefs](https://github.com/tortamque/Weathque/assets/90132962/33e5c053-7fcc-4080-819c-6d823adf6220) 39 | 40 | 41 | ## Features 42 | - Get the latest weather data using the [OpenWeatherMap API](https://openweathermap.org/api) for up-to-the-moment accuracy. 43 | - Access a 5-day weather forecast to plan ahead. 44 | - Get weather updates for a variety of cities, allowing you to stay informed about conditions worldwide. 45 | - Elegant design featuring captivating animations and a vibrant color palette. 46 | - Experience a responsive design that seamlessly adapts to different devices. 47 | - Easily add your own cities and track the weather in each of them. 48 | 49 | 50 | ## Supported platforms 51 | Weathque is a cross-platform mobile application that supports both Android and iOS platforms. 52 | 53 | 54 | ## Installation 55 | To run the Weathque app locally, follow these steps: 56 | 57 | 1. Clone this repository to your local machine. 58 | 2. Ensure you have Flutter and Dart installed on your system. 59 | 3. Navigate to the project directory in your terminal. 60 | 4. Run the command `flutter pub get` to install the required dependencies. 61 | 5. Connect a device or start an emulator. 62 | 6. Run the command `flutter run` to launch the app. 63 | 64 | 65 | ## Usage 66 | 1. Launch the Weathque app on your device or emulator. 67 | 2. Swipe right to navigate to the weather forecast of the next city. 68 | 3. Swipe left to move back to the weather forecast of the previous city. 69 | 4. For a 5-day weather forecast, simply swipe the "Weekly Forecast" cards. 70 | 5. To add your own city, follow these steps: 71 | * Tap the "Settings" icon on the app bar. 72 | * Enter your city name. 73 | * A new card will appear on the screen. 74 | * Close the Settings menu. 75 | * Swipe to your newly added city. 76 | 7. To remove unnecessary city tap "Settings" icon on the app bar 77 | * Tap the "Settings" icon on the app bar. 78 | * Swipe right or left on the city you want to remove. 79 | * The city will be removed. 80 | 81 | 82 | ## Screenshots and Demo 83 | ### Screenshots 84 | Screenshot_1 85 | Screenshot_2 86 | Screenshot_3 87 | Screenshot_4 88 | 89 | ### Demo 90 | https://github.com/tortamque/Weathque/assets/90132962/9016515a-ecdc-461d-abbc-47d882c64d4c 91 | 92 | 93 | ## Changelog 94 | ### [1.1] - 25.08.2023 95 | #### Added 96 | - Now you can easily add your own cities and track the weather in each of them. 🔮 97 | 98 | ### [1.0] - 16.08.2023 99 | #### Added 100 | - Initial release. 101 | 102 | 103 | ## Used Packages 104 | The Weathque app utilizes the following packages: 105 | 106 | | Name | Version | Link on pub.dev | 107 | |------------------------|---------|----------------------------------------------------------| 108 | | flutter_bloc | 8.1.3 | [Link](https://pub.dev/packages/flutter_bloc) | 109 | | retrofit | 4.0.1 | [Link](https://pub.dev/packages/retrofit) | 110 | | get_it | 7.6.0 | [Link](https://pub.dev/packages/get_it) | 111 | | intl | 0.18.1 | [Link](https://pub.dev/packages/intl) | 112 | | flutter_carousel_slider| 1.1.0 | [Link](https://pub.dev/packages/flutter_carousel_slider) | 113 | | fluttertoast | 8.2.2 | [Link](https://pub.dev/packages/fluttertoast) | 114 | | shared_preferences | 2.2.0 | [Link](https://pub.dev/packages/shared_preferences) | 115 | 116 | 117 | ## License 118 | Apache License Version 2.0 119 | -------------------------------------------------------------------------------- /lib/features/app/presentation/widgets/weekly_forecast/weekly_forecast.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:weathque/features/app/domain/entities/forecast_weather_entity.dart'; 4 | import 'package:weathque/features/app/presentation/widgets/animations/top_animation.dart'; 5 | import 'package:weathque/features/app/presentation/widgets/misc/headers/summary_header/header.dart'; 6 | import 'package:weathque/features/app/presentation/widgets/weekly_forecast/forecast_card.dart'; 7 | 8 | class WeeklyForecast extends StatefulWidget { 9 | final ForecastWeatherEntity? forecastWeatherEntity; 10 | 11 | const WeeklyForecast({ 12 | required this.forecastWeatherEntity, 13 | super.key 14 | }); 15 | 16 | @override 17 | State createState() => _WeeklyForecastState(); 18 | } 19 | 20 | class _WeeklyForecastState extends State { 21 | final DateTime currentDate = DateTime.now(); 22 | 23 | final Duration animationDuration = const Duration(milliseconds: 500); 24 | final Curve animationCurve = Curves.easeOutSine; 25 | bool isAnimated1 = false; 26 | bool isAnimated2 = false; 27 | bool isAnimated3 = false; 28 | bool isAnimated4 = false; 29 | bool isAnimated5 = false; 30 | 31 | @override 32 | void initState() { 33 | _startAnimation1(); 34 | _startAnimation2(); 35 | _startAnimation3(); 36 | _startAnimation4(); 37 | _startAnimation5(); 38 | super.initState(); 39 | } 40 | 41 | void _startAnimation1() async{ 42 | await Future.delayed(const Duration(milliseconds: 2700)) 43 | .then((value) => setState(() => isAnimated1 = true)); 44 | } 45 | 46 | void _startAnimation2() async{ 47 | await Future.delayed(const Duration(milliseconds: 2850)) 48 | .then((value) => setState(() => isAnimated2 = true)); 49 | } 50 | 51 | void _startAnimation3() async{ 52 | await Future.delayed(const Duration(milliseconds: 3000)) 53 | .then((value) => setState(() => isAnimated3 = true)); 54 | } 55 | 56 | void _startAnimation4() async{ 57 | await Future.delayed(const Duration(milliseconds: 3150)) 58 | .then((value) => setState(() => isAnimated4 = true)); 59 | } 60 | 61 | void _startAnimation5() async{ 62 | await Future.delayed(const Duration(milliseconds: 3300)) 63 | .then((value) => setState(() => isAnimated5 = true)); 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return widget.forecastWeatherEntity != null ? 69 | SingleChildScrollView( 70 | scrollDirection: Axis.horizontal, 71 | child: Row( 72 | crossAxisAlignment: CrossAxisAlignment.center, 73 | children: [ 74 | Padding( 75 | padding: const EdgeInsets.all(8.0), 76 | child: Stack( 77 | alignment: Alignment.topCenter, 78 | children: [ 79 | const SizedBox( 80 | height: 110, 81 | width: 67.5, 82 | ), 83 | 84 | TopAnimation( 85 | curve: animationCurve, 86 | duration: animationDuration, 87 | animationField: isAnimated1, 88 | positionInitialValue: MediaQuery.of(context).size.height/26, 89 | opacityInitialValue: 0, 90 | child: ForecastCard( 91 | temperature: widget.forecastWeatherEntity!.list[8 * 0 + 0].main.temp.round().toString(), 92 | iconPath: defineIcon(widget.forecastWeatherEntity!.list[8 * 0 + 6].weather[0].id), 93 | date: DateFormat('dd MMM').format(DateTime(currentDate.year, currentDate.month, currentDate.day + 1)) 94 | ) 95 | ) 96 | ], 97 | ), 98 | ), 99 | 100 | Padding( 101 | padding: const EdgeInsets.all(8.0), 102 | child: Stack( 103 | children: [ 104 | const SizedBox( 105 | height: 110, 106 | width: 67.5, 107 | ), 108 | 109 | TopAnimation( 110 | curve: animationCurve, 111 | duration: animationDuration, 112 | animationField: isAnimated2, 113 | positionInitialValue: MediaQuery.of(context).size.height/26, 114 | opacityInitialValue: 0, 115 | child: ForecastCard( 116 | temperature: widget.forecastWeatherEntity!.list[8 * 1 + 6].main.temp.round().toString(), 117 | iconPath: defineIcon(widget.forecastWeatherEntity!.list[8 * 1 + 6].weather[0].id), 118 | date: DateFormat('dd MMM').format(DateTime(currentDate.year, currentDate.month, currentDate.day + 2)) 119 | ), 120 | ), 121 | ], 122 | ), 123 | ), 124 | 125 | Padding( 126 | padding: const EdgeInsets.all(8.0), 127 | child: Stack( 128 | children: [ 129 | const SizedBox( 130 | height: 110, 131 | width: 67.5, 132 | ), 133 | 134 | TopAnimation( 135 | curve: animationCurve, 136 | duration: animationDuration, 137 | animationField: isAnimated3, 138 | positionInitialValue: MediaQuery.of(context).size.height/26, 139 | opacityInitialValue: 0, 140 | child: ForecastCard( 141 | temperature: widget.forecastWeatherEntity!.list[8 * 2 + 6].main.temp.round().toString(), 142 | iconPath: defineIcon(widget.forecastWeatherEntity!.list[8 * 2 + 6].weather[0].id), 143 | date: DateFormat('dd MMM').format(DateTime(currentDate.year, currentDate.month, currentDate.day + 3)) 144 | ), 145 | ), 146 | ], 147 | ), 148 | ), 149 | 150 | Padding( 151 | padding: const EdgeInsets.all(8.0), 152 | child: Stack( 153 | children: [ 154 | const SizedBox( 155 | height: 110, 156 | width: 67.5, 157 | ), 158 | 159 | TopAnimation( 160 | curve: animationCurve, 161 | duration: animationDuration, 162 | animationField: isAnimated4, 163 | positionInitialValue: MediaQuery.of(context).size.height/26, 164 | opacityInitialValue: 0, 165 | child: ForecastCard( 166 | temperature: widget.forecastWeatherEntity!.list[8 * 3 + 6].main.temp.round().toString(), 167 | iconPath: defineIcon(widget.forecastWeatherEntity!.list[8 * 3 + 6].weather[0].id), 168 | date: DateFormat('dd MMM').format(DateTime(currentDate.year, currentDate.month, currentDate.day + 4)) 169 | ), 170 | ), 171 | ], 172 | ), 173 | ), 174 | 175 | Padding( 176 | padding: const EdgeInsets.all(8.0), 177 | child: Stack( 178 | children: [ 179 | const SizedBox( 180 | height: 110, 181 | width: 67.5, 182 | ), 183 | 184 | TopAnimation( 185 | curve: animationCurve, 186 | duration: animationDuration, 187 | animationField: isAnimated5, 188 | positionInitialValue: MediaQuery.of(context).size.height/26, 189 | opacityInitialValue: 0, 190 | child: ForecastCard( 191 | temperature: widget.forecastWeatherEntity!.list[8 * 4 + 6].main.temp.round().toString(), 192 | iconPath: defineIcon(widget.forecastWeatherEntity!.list[8 * 4 + 6].weather[0].id), 193 | date: DateFormat('dd MMM').format(DateTime(currentDate.year, currentDate.month, currentDate.day + 5)) 194 | ), 195 | ), 196 | ], 197 | ), 198 | ), 199 | ], 200 | ), 201 | ) 202 | : Center( 203 | child: TopAnimation( 204 | curve: animationCurve, 205 | duration: animationDuration, 206 | animationField: isAnimated5, 207 | positionInitialValue: MediaQuery.of(context).size.height/26, 208 | opacityInitialValue: 0, 209 | child: const Header(text: "Weekly forecast data isn't available") 210 | ), 211 | ); 212 | } 213 | 214 | String defineIcon(int id){ 215 | switch(id){ 216 | case >= 200 && < 300: 217 | return "assets/icons/weather/lightning.png"; 218 | case >= 300 && < 400: 219 | return "assets/icons/weather/rain.png"; 220 | case >= 500 && < 600: 221 | return "assets/icons/weather/rain.png"; 222 | case >= 600 && < 700: 223 | return "assets/icons/weather/snow.png"; 224 | case >= 700 && < 800: 225 | return "assets/icons/weather/mist.png"; 226 | case 800: 227 | return "assets/icons/weather/sun.png"; 228 | case >800 && < 803: 229 | return "assets/icons/weather/partly-cloudy.png"; 230 | case >= 803: 231 | return "assets/icons/weather/cloud.png"; 232 | default: 233 | return "assets/icons/weather/unknown.png"; 234 | } 235 | } 236 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 46 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 47 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 49 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 50 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 51 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 57 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 9740EEB11CF90186004384FC /* Flutter */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 75 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 76 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 77 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 78 | ); 79 | name = Flutter; 80 | sourceTree = ""; 81 | }; 82 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 86 | ); 87 | path = RunnerTests; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807E294A63A400263BE5 /* Frameworks */, 133 | 331C807F294A63A400263BE5 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 139 | ); 140 | name = RunnerTests; 141 | productName = RunnerTests; 142 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 143 | productType = "com.apple.product-type.bundle.unit-test"; 144 | }; 145 | 97C146ED1CF9000F007C117D /* Runner */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 148 | buildPhases = ( 149 | 9740EEB61CF901F6004384FC /* Run Script */, 150 | 97C146EA1CF9000F007C117D /* Sources */, 151 | 97C146EB1CF9000F007C117D /* Frameworks */, 152 | 97C146EC1CF9000F007C117D /* Resources */, 153 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 154 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = Runner; 161 | productName = Runner; 162 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | 97C146E61CF9000F007C117D /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastUpgradeCheck = 1300; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNREACHABLE_CODE = YES; 333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 334 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 335 | COPY_PHASE_STRIP = NO; 336 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 337 | ENABLE_NS_ASSERTIONS = NO; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | GCC_C_LANGUAGE_STANDARD = gnu99; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 348 | MTL_ENABLE_DEBUG_INFO = NO; 349 | SDKROOT = iphoneos; 350 | SUPPORTED_PLATFORMS = iphoneos; 351 | TARGETED_DEVICE_FAMILY = "1,2"; 352 | VALIDATE_PRODUCT = YES; 353 | }; 354 | name = Profile; 355 | }; 356 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 357 | isa = XCBuildConfiguration; 358 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 359 | buildSettings = { 360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 361 | CLANG_ENABLE_MODULES = YES; 362 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 363 | ENABLE_BITCODE = NO; 364 | INFOPLIST_FILE = Runner/Info.plist; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/Frameworks", 368 | ); 369 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 372 | SWIFT_VERSION = 5.0; 373 | VERSIONING_SYSTEM = "apple-generic"; 374 | }; 375 | name = Profile; 376 | }; 377 | 331C8088294A63A400263BE5 /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; 380 | buildSettings = { 381 | BUNDLE_LOADER = "$(TEST_HOST)"; 382 | CODE_SIGN_STYLE = Automatic; 383 | CURRENT_PROJECT_VERSION = 1; 384 | GENERATE_INFOPLIST_FILE = YES; 385 | MARKETING_VERSION = 1.0; 386 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque.RunnerTests; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 389 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 390 | SWIFT_VERSION = 5.0; 391 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 392 | }; 393 | name = Debug; 394 | }; 395 | 331C8089294A63A400263BE5 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; 398 | buildSettings = { 399 | BUNDLE_LOADER = "$(TEST_HOST)"; 400 | CODE_SIGN_STYLE = Automatic; 401 | CURRENT_PROJECT_VERSION = 1; 402 | GENERATE_INFOPLIST_FILE = YES; 403 | MARKETING_VERSION = 1.0; 404 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque.RunnerTests; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SWIFT_VERSION = 5.0; 407 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 408 | }; 409 | name = Release; 410 | }; 411 | 331C808A294A63A400263BE5 /* Profile */ = { 412 | isa = XCBuildConfiguration; 413 | baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; 414 | buildSettings = { 415 | BUNDLE_LOADER = "$(TEST_HOST)"; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | GENERATE_INFOPLIST_FILE = YES; 419 | MARKETING_VERSION = 1.0; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque.RunnerTests; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | SWIFT_VERSION = 5.0; 423 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 424 | }; 425 | name = Profile; 426 | }; 427 | 97C147031CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_SEARCH_USER_PATHS = NO; 431 | CLANG_ANALYZER_NONNULL = YES; 432 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 433 | CLANG_CXX_LIBRARY = "libc++"; 434 | CLANG_ENABLE_MODULES = YES; 435 | CLANG_ENABLE_OBJC_ARC = YES; 436 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 437 | CLANG_WARN_BOOL_CONVERSION = YES; 438 | CLANG_WARN_COMMA = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = dwarf; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu99; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_OPTIMIZATION_LEVEL = 0; 464 | GCC_PREPROCESSOR_DEFINITIONS = ( 465 | "DEBUG=1", 466 | "$(inherited)", 467 | ); 468 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 469 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 470 | GCC_WARN_UNDECLARED_SELECTOR = YES; 471 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 472 | GCC_WARN_UNUSED_FUNCTION = YES; 473 | GCC_WARN_UNUSED_VARIABLE = YES; 474 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 475 | MTL_ENABLE_DEBUG_INFO = YES; 476 | ONLY_ACTIVE_ARCH = YES; 477 | SDKROOT = iphoneos; 478 | TARGETED_DEVICE_FAMILY = "1,2"; 479 | }; 480 | name = Debug; 481 | }; 482 | 97C147041CF9000F007C117D /* Release */ = { 483 | isa = XCBuildConfiguration; 484 | buildSettings = { 485 | ALWAYS_SEARCH_USER_PATHS = NO; 486 | CLANG_ANALYZER_NONNULL = YES; 487 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 488 | CLANG_CXX_LIBRARY = "libc++"; 489 | CLANG_ENABLE_MODULES = YES; 490 | CLANG_ENABLE_OBJC_ARC = YES; 491 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_COMMA = YES; 494 | CLANG_WARN_CONSTANT_CONVERSION = YES; 495 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 496 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 497 | CLANG_WARN_EMPTY_BODY = YES; 498 | CLANG_WARN_ENUM_CONVERSION = YES; 499 | CLANG_WARN_INFINITE_RECURSION = YES; 500 | CLANG_WARN_INT_CONVERSION = YES; 501 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 502 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 503 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 505 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 506 | CLANG_WARN_STRICT_PROTOTYPES = YES; 507 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 508 | CLANG_WARN_UNREACHABLE_CODE = YES; 509 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 510 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 511 | COPY_PHASE_STRIP = NO; 512 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 513 | ENABLE_NS_ASSERTIONS = NO; 514 | ENABLE_STRICT_OBJC_MSGSEND = YES; 515 | GCC_C_LANGUAGE_STANDARD = gnu99; 516 | GCC_NO_COMMON_BLOCKS = YES; 517 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 518 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 519 | GCC_WARN_UNDECLARED_SELECTOR = YES; 520 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 521 | GCC_WARN_UNUSED_FUNCTION = YES; 522 | GCC_WARN_UNUSED_VARIABLE = YES; 523 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 524 | MTL_ENABLE_DEBUG_INFO = NO; 525 | SDKROOT = iphoneos; 526 | SUPPORTED_PLATFORMS = iphoneos; 527 | SWIFT_COMPILATION_MODE = wholemodule; 528 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 529 | TARGETED_DEVICE_FAMILY = "1,2"; 530 | VALIDATE_PRODUCT = YES; 531 | }; 532 | name = Release; 533 | }; 534 | 97C147061CF9000F007C117D /* Debug */ = { 535 | isa = XCBuildConfiguration; 536 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 537 | buildSettings = { 538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 539 | CLANG_ENABLE_MODULES = YES; 540 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 541 | ENABLE_BITCODE = NO; 542 | INFOPLIST_FILE = Runner/Info.plist; 543 | LD_RUNPATH_SEARCH_PATHS = ( 544 | "$(inherited)", 545 | "@executable_path/Frameworks", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 550 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 551 | SWIFT_VERSION = 5.0; 552 | VERSIONING_SYSTEM = "apple-generic"; 553 | }; 554 | name = Debug; 555 | }; 556 | 97C147071CF9000F007C117D /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 559 | buildSettings = { 560 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 561 | CLANG_ENABLE_MODULES = YES; 562 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 563 | ENABLE_BITCODE = NO; 564 | INFOPLIST_FILE = Runner/Info.plist; 565 | LD_RUNPATH_SEARCH_PATHS = ( 566 | "$(inherited)", 567 | "@executable_path/Frameworks", 568 | ); 569 | PRODUCT_BUNDLE_IDENTIFIER = com.example.weathque; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 572 | SWIFT_VERSION = 5.0; 573 | VERSIONING_SYSTEM = "apple-generic"; 574 | }; 575 | name = Release; 576 | }; 577 | /* End XCBuildConfiguration section */ 578 | 579 | /* Begin XCConfigurationList section */ 580 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 331C8088294A63A400263BE5 /* Debug */, 584 | 331C8089294A63A400263BE5 /* Release */, 585 | 331C808A294A63A400263BE5 /* Profile */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | 97C147031CF9000F007C117D /* Debug */, 594 | 97C147041CF9000F007C117D /* Release */, 595 | 249021D3217E4FDB00AE95B9 /* Profile */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 97C147061CF9000F007C117D /* Debug */, 604 | 97C147071CF9000F007C117D /* Release */, 605 | 249021D4217E4FDB00AE95B9 /* Profile */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | /* End XCConfigurationList section */ 611 | }; 612 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 613 | } 614 | --------------------------------------------------------------------------------