├── 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-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.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 │ │ ├── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ └── Contents.json │ │ └── BrandingImage.imageset │ │ │ └── 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 └── .gitignore ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── splash │ ├── img │ │ ├── dark-1x.png │ │ ├── dark-2x.png │ │ ├── dark-3x.png │ │ ├── dark-4x.png │ │ ├── light-1x.png │ │ ├── light-2x.png │ │ ├── light-3x.png │ │ └── light-4x.png │ └── style.css ├── manifest.json └── index.html ├── ic_matercard.png ├── images ├── splash.png └── pokemon.svg ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable-hdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ ├── background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── 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 │ │ │ │ ├── values-night │ │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── pokedex │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── .metadata ├── lib ├── presentation │ ├── core │ │ └── app_colors.dart │ ├── cubit │ │ ├── pokemon_state.dart │ │ ├── favorite_pokemon_state.dart │ │ ├── pokemon_cubit.dart │ │ └── favorite_pokemon_cubit.dart │ ├── util.dart │ ├── widgets │ │ ├── pokemon_attribute_card.dart │ │ ├── pokemon_stat.dart │ │ └── pokemon_card.dart │ └── pages │ │ ├── home_page.dart │ │ └── pokemon_details.dart ├── domain │ ├── repositories │ │ └── pokemon_repository.dart │ └── entities │ │ └── pokemon.dart ├── data │ ├── network │ │ └── pokemon_rest_client.dart │ ├── datasources │ │ ├── pokemon_remote_datasource.dart │ │ └── pokemon_local_datasource.dart │ └── repositories │ │ └── pokemon_repository.dart ├── main.dart └── injection_container.dart ├── .gitignore ├── test ├── data │ ├── datasources │ │ ├── pokemon_remote_datasource_test.dart │ │ └── pokemon_local_datasource_test.dart │ └── repositories │ │ └── pokemon_repository_test.dart └── presentation │ └── cubits │ ├── pokemon_cubit_test.dart │ └── favorite_pokemon_cubit_test.dart ├── analysis_options.yaml ├── README.md ├── pubspec.yaml └── pubspec.lock /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 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/favicon.png -------------------------------------------------------------------------------- /ic_matercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/ic_matercard.png -------------------------------------------------------------------------------- /images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/images/splash.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/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/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/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/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abu-muhab/pokedex/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/Abu-muhab/pokedex/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/Abu-muhab/pokedex/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/pokedex/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.pokedex 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 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 and should not be manually edited. 5 | 6 | version: 7 | revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /lib/presentation/core/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color grey = Color(0xFF6B6B6B); 5 | static const Color black = Colors.black; 6 | static const Color darkBlue = Color(0xFF161A33); 7 | static const Color lightBlue = Color(0xFF3558CD); 8 | static const Color lightBlueAccent = Color(0xFFD5DEFF); 9 | static const Color primaryColor = Color(0xFF3558CD); 10 | } 11 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/cubit/pokemon_state.dart: -------------------------------------------------------------------------------- 1 | part of 'pokemon_cubit.dart'; 2 | 3 | @freezed 4 | class PokemonState with _$PokemonState { 5 | const factory PokemonState.initial() = PokemonInitialState; 6 | const factory PokemonState.loading() = PokemonLoadingState; 7 | const factory PokemonState.loadingMore(List pokemons) = 8 | PokemonLoadingMoreState; 9 | const factory PokemonState.loaded(List pokemons) = 10 | PokemonLoadedState; 11 | const factory PokemonState.error(String message) = PokemonErrorState; 12 | } 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BrandingImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "BrandingImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "BrandingImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/cubit/favorite_pokemon_state.dart: -------------------------------------------------------------------------------- 1 | part of 'favorite_pokemon_cubit.dart'; 2 | 3 | @freezed 4 | class FavoritePokemonState with _$FavoritePokemonState { 5 | const factory FavoritePokemonState.initial() = FavoritePokemonInitialState; 6 | const factory FavoritePokemonState.loading() = FavoritePokemonLoadingState; 7 | const factory FavoritePokemonState.loaded( 8 | {List? pokemon, 9 | bool? isCachedData}) = FavoritePokemonLoadedState; 10 | const factory FavoritePokemonState.error(String message) = 11 | FavoritePokemonErrorState; 12 | } 13 | -------------------------------------------------------------------------------- /lib/presentation/util.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:palette_generator/palette_generator.dart'; 4 | 5 | Future computeDominatImageColor(String imageUrl) async { 6 | final PaletteGenerator paletteGenerator = 7 | await PaletteGenerator.fromImageProvider( 8 | CachedNetworkImageProvider(imageUrl), 9 | ); 10 | return paletteGenerator.dominantColor?.color; 11 | } 12 | 13 | extension StringExtention on String { 14 | String capitalizeFirstLetter() { 15 | return "${this[0].toUpperCase()}${substring(1)}"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.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 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /web/splash/style.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; 3 | height:100%; 4 | background: #3558CD; 5 | 6 | background-size: 100% 100%; 7 | } 8 | 9 | .center { 10 | margin: 0; 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | -ms-transform: translate(-50%, -50%); 15 | transform: translate(-50%, -50%); 16 | } 17 | 18 | .contain { 19 | display:block; 20 | width:100%; height:100%; 21 | object-fit: contain; 22 | } 23 | 24 | .stretch { 25 | display:block; 26 | width:100%; height:100%; 27 | } 28 | 29 | .cover { 30 | display:block; 31 | width:100%; height:100%; 32 | object-fit: cover; 33 | } 34 | 35 | @media (prefers-color-scheme: dark) { 36 | body { 37 | margin:0; 38 | height:100%; 39 | background: #3558CD; 40 | 41 | background-size: 100% 100%; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/presentation/widgets/pokemon_attribute_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pokedex/presentation/core/app_colors.dart'; 3 | 4 | class PokemonAttributeCard extends StatelessWidget { 5 | final String? title; 6 | final String? value; 7 | const PokemonAttributeCard({Key? key, this.value, this.title}) 8 | : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Column(children: [ 13 | Text( 14 | title ?? '', 15 | style: const TextStyle( 16 | fontWeight: FontWeight.w500, color: AppColors.grey, fontSize: 12), 17 | ), 18 | const SizedBox(height: 10), 19 | Text( 20 | value ?? '', 21 | style: const TextStyle(fontWeight: FontWeight.w400, fontSize: 14), 22 | ), 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/domain/repositories/pokemon_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:pokedex/domain/entities/pokemon.dart'; 3 | 4 | part 'pokemon_repository.freezed.dart'; 5 | 6 | abstract class PokemonRepository { 7 | Future> getPokemons({int offset = 0, int limit = 20}); 8 | Future get(dynamic nameOrId); 9 | Future getFavoritePokemons( 10 | {bool? invalidateCache = false}); 11 | Future removeFavoritePokemon(Pokemon pokemon); 12 | Future updateFavoritePokemon(Pokemon pokemon); 13 | Future addFavoritePokemon(Pokemon pokemon); 14 | Future isFavoritePokemon(Pokemon pokemon); 15 | } 16 | 17 | @freezed 18 | class GetFavoritePokemonResponse with _$GetFavoritePokemonResponse { 19 | factory GetFavoritePokemonResponse(List? pokemons, bool? isCached) = 20 | _GetFavoritePokemonResponse; 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | #generated flutter files 49 | *.g.dart 50 | *.freezed.dart 51 | *.mocks.dart 52 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pokedex", 3 | "short_name": "pokedex", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/presentation/cubit/pokemon_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:pokedex/domain/entities/pokemon.dart'; 4 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 5 | 6 | part 'pokemon_state.dart'; 7 | part 'pokemon_cubit.freezed.dart'; 8 | 9 | class PokemonCubit extends Cubit { 10 | final PokemonRepository pokemonRepository; 11 | PokemonCubit({required this.pokemonRepository}) 12 | : super(const PokemonState.initial()); 13 | 14 | Future loadPokemons({List? loadedPokemons}) async { 15 | if (loadedPokemons == null) { 16 | emit(const PokemonState.loading()); 17 | } else { 18 | emit(PokemonState.loadingMore(loadedPokemons)); 19 | } 20 | 21 | try { 22 | final pokemons = await pokemonRepository.getPokemons( 23 | offset: loadedPokemons?.length ?? 0); 24 | emit(PokemonState.loaded([...?loadedPokemons, ...pokemons])); 25 | } catch (e) { 26 | emit(const PokemonState.error("Something went wrong")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | -------------------------------------------------------------------------------- /lib/data/network/pokemon_rest_client.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:pokedex/domain/entities/pokemon.dart'; 3 | import 'package:retrofit/retrofit.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | 6 | part 'pokemon_rest_client.g.dart'; 7 | part 'pokemon_rest_client.freezed.dart'; 8 | 9 | @RestApi(baseUrl: "https://pokeapi.co/api/v2/") 10 | abstract class PokemonRestClient { 11 | factory PokemonRestClient(Dio dio, {String baseUrl}) = _PokemonRestClient; 12 | @GET("pokemon") 13 | Future getPokemons( 14 | {@Query('offset') int offset = 0, @Query('limit') int limit = 15}); 15 | 16 | @GET("pokemon/{nameOrId}") 17 | Future getPokemon(@Path("nameOrId") dynamic nameOrId); 18 | } 19 | 20 | @freezed 21 | class GetPokemonsResponse with _$GetPokemonsResponse { 22 | factory GetPokemonsResponse({ 23 | required int count, 24 | required String next, 25 | required List results, 26 | }) = _GetPokemonsResponse; 27 | 28 | factory GetPokemonsResponse.fromJson(Map json) => 29 | _$GetPokemonsResponseFromJson(json); 30 | } 31 | -------------------------------------------------------------------------------- /images/pokemon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/data/datasources/pokemon_remote_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:mockito/annotations.dart'; 5 | import 'package:mockito/mockito.dart'; 6 | import 'package:pokedex/data/datasources/pokemon_remote_datasource.dart'; 7 | import 'package:pokedex/data/network/pokemon_rest_client.dart'; 8 | 9 | import 'pokemon_remote_datasource_test.mocks.dart'; 10 | 11 | @GenerateMocks([PokemonRestClient]) 12 | void main() { 13 | late MockPokemonRestClient mockPokemonRestClient; 14 | late PokemonRemoteDataSourceImpl pokemonRemoteDataSource; 15 | 16 | setUp(() { 17 | mockPokemonRestClient = MockPokemonRestClient(); 18 | pokemonRemoteDataSource = PokemonRemoteDataSourceImpl( 19 | mockPokemonRestClient, 20 | ); 21 | }); 22 | group('pokemonRemoteDataSource', () { 23 | test('should throw an error when offline', () async { 24 | when(mockPokemonRestClient.getPokemons()) 25 | .thenThrow(const SocketException('No Internet')); 26 | when(mockPokemonRestClient.getPokemon('poke_poke')) 27 | .thenThrow(const SocketException('No Internet')); 28 | 29 | expect(() => pokemonRemoteDataSource.get('poke_poke'), throwsException); 30 | expect(() => pokemonRemoteDataSource.getPokemons(), throwsException); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:pokedex/presentation/cubit/favorite_pokemon_cubit.dart'; 4 | import 'package:pokedex/presentation/cubit/pokemon_cubit.dart'; 5 | import 'package:pokedex/presentation/pages/home_page.dart'; 6 | import 'package:pokedex/presentation/pages/pokemon_details.dart'; 7 | 8 | import 'injection_container.dart'; 9 | 10 | void main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await init(); 13 | runApp(const MyApp()); 14 | } 15 | 16 | class MyApp extends StatelessWidget { 17 | const MyApp({Key? key}) : super(key: key); 18 | @override 19 | Widget build(BuildContext context) { 20 | return MultiBlocProvider( 21 | providers: [ 22 | BlocProvider( 23 | create: (BuildContext context) => sl(), 24 | ), 25 | BlocProvider( 26 | create: (BuildContext context) => sl(), 27 | ) 28 | ], 29 | child: MaterialApp( 30 | debugShowCheckedModeBanner: false, 31 | title: 'Pokedex', 32 | routes: { 33 | '/': (context) => const HomePage(), 34 | '/details': (context) => const PokemonDetailsPage() 35 | }, 36 | initialRoute: '/', 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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/injection_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:get_it/get_it.dart'; 3 | import 'package:pokedex/data/datasources/pokemon_local_datasource.dart'; 4 | import 'package:pokedex/data/datasources/pokemon_remote_datasource.dart'; 5 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 6 | import 'package:pokedex/presentation/cubit/favorite_pokemon_cubit.dart'; 7 | import 'package:pokedex/presentation/cubit/pokemon_cubit.dart'; 8 | import 'package:shared_preferences/shared_preferences.dart'; 9 | 10 | import 'data/network/pokemon_rest_client.dart'; 11 | import 'data/repositories/pokemon_repository.dart'; 12 | 13 | final sl = GetIt.instance; 14 | 15 | Future init() async { 16 | //cubits 17 | sl.registerFactory(() => PokemonCubit(pokemonRepository: sl())); 18 | sl.registerFactory(() => FavoritePokemonCubit(pokemonRepository: sl())); 19 | 20 | //repositories 21 | sl.registerLazySingleton(() => PokemonRepositoryImpl( 22 | pokemonRemoteDataSource: sl(), pokemonLocalDatasource: sl())); 23 | 24 | //data sources 25 | sl.registerLazySingleton( 26 | () => PokemonRemoteDataSourceImpl(sl())); 27 | sl.registerLazySingleton( 28 | () => PokemonLocalDatasourceImpl(sl())); 29 | 30 | //network 31 | sl.registerLazySingleton(() => PokemonRestClient(sl())); 32 | 33 | //external 34 | sl.registerLazySingleton(() => Dio()); 35 | SharedPreferences preferences = await SharedPreferences.getInstance(); 36 | sl.registerLazySingleton(() => preferences); 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pokedex 2 | A pokemon app that let's you explore a large libarary of pokemons, view their strenghts and save your favorite pokemons!. (Built using freezed and flutter_bloc) 3 | 4 | # Running instructions 5 | * step 1 ```flutter pub get``` 6 | * step 2 ```flutter pub run build_runner build``` 7 | * step 3 ```flutter run``` 8 | 9 | ## Packages 10 | * [flutter_bloc](https://pub.dev/packages/flutter_bloc) 11 | * [bloc](https://pub.dev/packages/bloc) 12 | * [retrofit](https://pub.dev/packages/retrofit) 13 | * [dio](https://pub.dev/packages/dio) 14 | * [get_it](https://pub.dev/packages/get_it) 15 | * [flutter_svg](https://pub.dev/packages/flutter_svg) 16 | * [palette_generator](https://pub.dev/packages/palette_generator) 17 | * [shared_preferences](https://pub.dev/packages/shared_preferences) 18 | * [responsive_builder](https://pub.dev/packages/responsive_builder) 19 | 20 |

Screenshots

21 |

22 | 23 | 24 |
25 | 26 | ## Author 27 | Abdulmalik 28 | 29 | -------------------------------------------------------------------------------- /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/data/datasources/pokemon_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:pokedex/data/network/pokemon_rest_client.dart'; 2 | import 'package:pokedex/domain/entities/pokemon.dart'; 3 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 4 | 5 | abstract class PokemonRemoteDataSource extends PokemonRepository {} 6 | 7 | class PokemonRemoteDataSourceImpl implements PokemonRemoteDataSource { 8 | final PokemonRestClient _pokemonRestClient; 9 | PokemonRemoteDataSourceImpl(this._pokemonRestClient); 10 | 11 | @override 12 | Future get(dynamic nameOrId) { 13 | return _pokemonRestClient.getPokemon(nameOrId); 14 | } 15 | 16 | @override 17 | Future> getPokemons({int offset = 0, int limit = 15}) async { 18 | final response = 19 | await _pokemonRestClient.getPokemons(offset: offset, limit: limit); 20 | List> pokemonsFutures = response.results 21 | .map((e) => _pokemonRestClient.getPokemon(e.name)) 22 | .toList(); 23 | return await Future.wait(pokemonsFutures); 24 | } 25 | 26 | @override 27 | Future addFavoritePokemon(Pokemon pokemon) { 28 | throw UnimplementedError(); 29 | } 30 | 31 | @override 32 | Future getFavoritePokemons( 33 | {bool? invalidateCache}) { 34 | throw UnimplementedError(); 35 | } 36 | 37 | @override 38 | Future removeFavoritePokemon(Pokemon pokemon) { 39 | throw UnimplementedError(); 40 | } 41 | 42 | @override 43 | Future updateFavoritePokemon(Pokemon pokemon) { 44 | throw UnimplementedError(); 45 | } 46 | 47 | @override 48 | Future isFavoritePokemon(Pokemon pokemon) { 49 | throw UnimplementedError(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/domain/entities/pokemon.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'pokemon.freezed.dart'; 6 | part 'pokemon.g.dart'; 7 | 8 | @freezed 9 | class Pokemon with _$Pokemon { 10 | const Pokemon._(); 11 | factory Pokemon( 12 | {String? name, 13 | String? url, 14 | int? id, 15 | List? types, 16 | String? imageUrl, 17 | int? height, 18 | int? weight, 19 | List? stats}) = _Pokemon; 20 | 21 | factory Pokemon.fromJson(Map json) => 22 | _$PokemonFromJson(json).copyWith( 23 | imageUrl: json['sprites']?['other']?['official-artwork'] 24 | ?['front_default'] ?? 25 | json['imageUrl']); 26 | 27 | double averagePower() { 28 | double total = 0; 29 | if (stats == null) { 30 | return 0; 31 | } 32 | 33 | for (Stat stat in stats!) { 34 | total += stat.baseStat!; 35 | } 36 | 37 | return total / stats!.length; 38 | } 39 | 40 | double getBMI() { 41 | return (weight ?? 0) / (pow(height ?? 0, 2)); 42 | } 43 | } 44 | 45 | @freezed 46 | class PokemonType with _$PokemonType { 47 | factory PokemonType({String? name, String? url}) = _PokemonType; 48 | 49 | factory PokemonType.fromJson(Map json) => 50 | _$PokemonTypeFromJson(json['type'] ?? json); 51 | } 52 | 53 | @freezed 54 | class Stat with _$Stat { 55 | factory Stat( 56 | {int? effort, 57 | @JsonKey(name: 'base_stat') int? baseStat, 58 | String? name}) = _Stat; 59 | 60 | factory Stat.fromJson(Map json) => 61 | _$StatFromJson(json).copyWith( 62 | name: json['stat']?['name'] ?? json['name'], 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/presentation/widgets/pokemon_stat.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pokedex/presentation/core/app_colors.dart'; 3 | import 'package:pokedex/presentation/util.dart'; 4 | 5 | class PokemonStat extends StatelessWidget { 6 | final String? title; 7 | final int? value; 8 | const PokemonStat({Key? key, required this.title, required this.value}) 9 | : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | crossAxisAlignment: CrossAxisAlignment.start, 15 | children: [ 16 | Row( 17 | children: [ 18 | Text( 19 | title?.capitalizeFirstLetter() ?? '', 20 | style: const TextStyle( 21 | fontWeight: FontWeight.w400, 22 | fontSize: 14, 23 | color: AppColors.grey), 24 | ), 25 | const SizedBox(width: 8), 26 | Text( 27 | value?.toString() ?? '0', 28 | style: const TextStyle( 29 | fontWeight: FontWeight.w500, 30 | fontSize: 14, 31 | color: AppColors.black), 32 | ) 33 | ], 34 | ), 35 | const SizedBox( 36 | height: 15, 37 | ), 38 | LinearProgressIndicator( 39 | value: (value?.toDouble() ?? 0.0) / 100, 40 | backgroundColor: Colors.grey[100], 41 | color: getColor((value?.toDouble() ?? 0.0) / 100), 42 | ) 43 | ], 44 | ); 45 | } 46 | } 47 | 48 | Color getColor(double percentage) { 49 | if (percentage < 0.25) { 50 | return Colors.red; 51 | } else if (percentage < 0.5) { 52 | return Colors.orange; 53 | } else if (percentage < 0.75) { 54 | return Colors.yellow; 55 | } else { 56 | return Colors.green; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Pokedex 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | pokedex 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 | UIStatusBarHidden 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/presentation/cubit/favorite_pokemon_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:pokedex/domain/entities/pokemon.dart'; 4 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 5 | 6 | part 'favorite_pokemon_state.dart'; 7 | part 'favorite_pokemon_cubit.freezed.dart'; 8 | 9 | class FavoritePokemonCubit extends Cubit { 10 | final PokemonRepository pokemonRepository; 11 | FavoritePokemonCubit({required this.pokemonRepository}) 12 | : super(const FavoritePokemonState.initial()); 13 | 14 | Future loadFavorites({bool? invalidateCache = false}) async { 15 | if (state is! FavoritePokemonLoadedState && 16 | state is! FavoritePokemonLoadingState) { 17 | emit(const FavoritePokemonState.loading()); 18 | } 19 | 20 | try { 21 | final response = await pokemonRepository.getFavoritePokemons( 22 | invalidateCache: invalidateCache); 23 | emit(FavoritePokemonState.loaded( 24 | pokemon: response.pokemons!, isCachedData: response.isCached!)); 25 | } catch (e) { 26 | emit(FavoritePokemonState.error(e.toString())); 27 | } 28 | } 29 | 30 | Future addToFavorites(Pokemon pokemon) async { 31 | emit(const FavoritePokemonState.loading()); 32 | try { 33 | await pokemonRepository.addFavoritePokemon(pokemon); 34 | loadFavorites(); 35 | } catch (e) { 36 | emit(FavoritePokemonState.error(e.toString())); 37 | } 38 | } 39 | 40 | Future removeFromFavorites(Pokemon pokemon) async { 41 | emit(const FavoritePokemonState.loading()); 42 | try { 43 | await pokemonRepository.removeFavoritePokemon(pokemon); 44 | loadFavorites(); 45 | } catch (e) { 46 | emit(FavoritePokemonState.error(e.toString())); 47 | } 48 | } 49 | 50 | Future isFavorite(Pokemon pokemon) async { 51 | return await pokemonRepository.isFavoritePokemon(pokemon); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/presentation/cubits/pokemon_cubit_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 5 | import 'package:pokedex/presentation/cubit/pokemon_cubit.dart'; 6 | 7 | import 'pokemon_cubit_test.mocks.dart'; 8 | 9 | @GenerateMocks([PokemonRepository]) 10 | void main() { 11 | late MockPokemonRepository pokemonRepository; 12 | late PokemonCubit pokemonCubit; 13 | setUp(() { 14 | pokemonRepository = MockPokemonRepository(); 15 | pokemonCubit = PokemonCubit(pokemonRepository: pokemonRepository); 16 | when(pokemonRepository.getPokemons()).thenAnswer((_) => Future.value([])); 17 | }); 18 | group('pokemonCubit', () { 19 | test( 20 | 'loadPokemons should emit [PokemonLoadingState,PokemonErrorState] states when data is fetched succesfully', 21 | () async { 22 | when(pokemonRepository.getPokemons()).thenThrow(Exception()); 23 | 24 | final expectedStates = [ 25 | const PokemonLoadingState(), 26 | const PokemonErrorState("Something went wrong") 27 | ]; 28 | 29 | expectLater(pokemonCubit.stream, emitsInOrder(expectedStates)); 30 | 31 | pokemonCubit.loadPokemons(); 32 | }); 33 | 34 | test( 35 | 'loadPokemons should emit [PokemonLoadingState,PokemonLoadedState] states when data is fetched succesfully', 36 | () async { 37 | final expectedStates = [ 38 | const PokemonLoadingState(), 39 | const PokemonLoadedState([]) 40 | ]; 41 | 42 | expectLater(pokemonCubit.stream, emitsInOrder(expectedStates)); 43 | 44 | pokemonCubit.loadPokemons(); 45 | }); 46 | 47 | test( 48 | 'loadPokemons should emit [PokemonLoadingState,PokemonLoadedState] states when more data is fetched succesfully', 49 | () async { 50 | final expectedStates = [ 51 | const PokemonLoadingMoreState([]), 52 | const PokemonLoadedState([]) 53 | ]; 54 | 55 | expectLater(pokemonCubit.stream, emitsInOrder(expectedStates)); 56 | 57 | pokemonCubit.loadPokemons(loadedPokemons: []); 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /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 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.pokedex" 47 | minSdkVersion flutter.minSdkVersion 48 | targetSdkVersion flutter.targetSdkVersion 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /test/presentation/cubits/favorite_pokemon_cubit_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:pokedex/domain/entities/pokemon.dart'; 5 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 6 | import 'package:pokedex/presentation/cubit/favorite_pokemon_cubit.dart'; 7 | 8 | import 'favorite_pokemon_cubit_test.mocks.dart'; 9 | 10 | @GenerateMocks([PokemonRepository]) 11 | void main() { 12 | late MockPokemonRepository pokemonRepository; 13 | late FavoritePokemonCubit favouritePokemonCubit; 14 | final expectedSuccessStates = [ 15 | const FavoritePokemonLoadingState(), 16 | const FavoritePokemonLoadedState(pokemon: [], isCachedData: true) 17 | ]; 18 | 19 | setUp(() { 20 | pokemonRepository = MockPokemonRepository(); 21 | favouritePokemonCubit = 22 | FavoritePokemonCubit(pokemonRepository: pokemonRepository); 23 | when(pokemonRepository.getFavoritePokemons()) 24 | .thenAnswer((_) => Future.value(GetFavoritePokemonResponse([], true))); 25 | when(pokemonRepository.removeFavoritePokemon(any)) 26 | .thenAnswer((_) => Future.value()); 27 | when(pokemonRepository.addFavoritePokemon(any)) 28 | .thenAnswer((_) => Future.value()); 29 | }); 30 | group('favouritePokemonCubit', () { 31 | test( 32 | 'loadFavorites should emit [FavoritePokemonLoadingState,FavoritePokemonLoadingState] states when data is fetched succesfully', 33 | () async { 34 | expectLater( 35 | favouritePokemonCubit.stream, emitsInOrder(expectedSuccessStates)); 36 | 37 | favouritePokemonCubit.loadFavorites(); 38 | }); 39 | 40 | test( 41 | 'addToFavorites should emit [FavoritePokemonLoadingState,FavoritePokemonLoadingState] states when pokemon is added succesfully', 42 | () async { 43 | expectLater( 44 | favouritePokemonCubit.stream, emitsInOrder(expectedSuccessStates)); 45 | 46 | favouritePokemonCubit.addToFavorites(Pokemon(id: 1, name: 'fave')); 47 | }); 48 | 49 | test( 50 | 'removeFromFavorites should emit [FavoritePokemonLoadingState,FavoritePokemonLoadingState] states when pokemon is removed succesfully', 51 | () async { 52 | expectLater( 53 | favouritePokemonCubit.stream, emitsInOrder(expectedSuccessStates)); 54 | 55 | favouritePokemonCubit.removeFromFavorites(Pokemon(id: 1, name: 'fave')); 56 | }); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /lib/data/repositories/pokemon_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:pokedex/data/datasources/pokemon_local_datasource.dart'; 2 | import 'package:pokedex/data/datasources/pokemon_remote_datasource.dart'; 3 | import 'package:pokedex/domain/entities/pokemon.dart'; 4 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 5 | 6 | class PokemonRepositoryImpl extends PokemonRepository { 7 | final PokemonRemoteDataSource pokemonRemoteDataSource; 8 | final PokemonLocalDatasource pokemonLocalDatasource; 9 | PokemonRepositoryImpl( 10 | {required this.pokemonRemoteDataSource, 11 | required this.pokemonLocalDatasource}); 12 | @override 13 | Future get(dynamic nameOrId) { 14 | return pokemonRemoteDataSource.get(nameOrId); 15 | } 16 | 17 | @override 18 | Future> getPokemons({int offset = 0, int limit = 20}) { 19 | return pokemonRemoteDataSource.getPokemons(offset: offset, limit: limit); 20 | } 21 | 22 | @override 23 | Future addFavoritePokemon(Pokemon pokemon) { 24 | return pokemonLocalDatasource.addFavoritePokemon(pokemon); 25 | } 26 | 27 | @override 28 | Future isFavoritePokemon(Pokemon pokemon) { 29 | return pokemonLocalDatasource.isFavoritePokemon(pokemon); 30 | } 31 | 32 | @override 33 | Future updateFavoritePokemon(Pokemon pokemon) { 34 | return pokemonLocalDatasource.updateFavoritePokemon(pokemon); 35 | } 36 | 37 | @override 38 | Future removeFavoritePokemon(Pokemon pokemon) { 39 | return pokemonLocalDatasource.removeFavoritePokemon(pokemon); 40 | } 41 | 42 | @override 43 | Future getFavoritePokemons( 44 | {bool? invalidateCache = false}) async { 45 | final response = await pokemonLocalDatasource.getFavoritePokemons(); 46 | if (response.pokemons == null) { 47 | return GetFavoritePokemonResponse([], true); 48 | } 49 | 50 | if (invalidateCache == false) { 51 | return response; 52 | } 53 | 54 | //try to get the latest remote data for the favourites 55 | try { 56 | List> futures = []; 57 | for (var pokemon in response.pokemons!) { 58 | futures.add(get(pokemon.name)); 59 | } 60 | 61 | //update the favorurites in local storage after successful fetch 62 | final remoteFavorites = await Future.wait(futures, eagerError: true); 63 | for (var element in remoteFavorites) { 64 | updateFavoritePokemon(element); 65 | } 66 | return GetFavoritePokemonResponse(remoteFavorites, false); 67 | } catch (err) { 68 | return response; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/data/repositories/pokemon_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:pokedex/data/datasources/pokemon_local_datasource.dart'; 5 | import 'package:pokedex/data/datasources/pokemon_remote_datasource.dart'; 6 | import 'package:pokedex/data/repositories/pokemon_repository.dart'; 7 | import 'package:pokedex/domain/entities/pokemon.dart'; 8 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 9 | 10 | import 'pokemon_repository_test.mocks.dart'; 11 | 12 | @GenerateMocks([PokemonRemoteDataSource, PokemonLocalDatasource]) 13 | void main() { 14 | late MockPokemonRemoteDataSource mockRemoteDataSource; 15 | late MockPokemonLocalDatasource mockLocalDataSource; 16 | late PokemonRepositoryImpl pokemonRepository; 17 | 18 | setUp(() { 19 | mockRemoteDataSource = MockPokemonRemoteDataSource(); 20 | mockLocalDataSource = MockPokemonLocalDatasource(); 21 | pokemonRepository = PokemonRepositoryImpl( 22 | pokemonRemoteDataSource: mockRemoteDataSource, 23 | pokemonLocalDatasource: mockLocalDataSource, 24 | ); 25 | }); 26 | group('pokemonRepository', () { 27 | test( 28 | 'getFavoritePokemons should return cached favorites when offline or remote source error', 29 | () async { 30 | when(mockRemoteDataSource.get(any)).thenThrow(Exception()); 31 | when(mockRemoteDataSource.getFavoritePokemons()).thenThrow(Exception()); 32 | when(mockLocalDataSource.getFavoritePokemons()).thenAnswer((_) async => 33 | GetFavoritePokemonResponse([Pokemon(name: 'pokemon')], true)); 34 | 35 | final response = 36 | await pokemonRepository.getFavoritePokemons(invalidateCache: true); 37 | 38 | expect(response.isCached, isTrue); 39 | }); 40 | 41 | test( 42 | 'getFavoritePokemons should return latest favorites remote data when network available', 43 | () async { 44 | when(mockRemoteDataSource.get(any)) 45 | .thenAnswer((_) async => Pokemon(name: 'new_pokemon', id: 1)); 46 | when(mockLocalDataSource.getFavoritePokemons()).thenAnswer((_) async => 47 | GetFavoritePokemonResponse( 48 | [Pokemon(name: 'old_pokemon', id: 1)], true)); 49 | 50 | final response = 51 | await pokemonRepository.getFavoritePokemons(invalidateCache: true); 52 | 53 | expect(response.isCached, isFalse); 54 | expect(response.pokemons!.first.name, 55 | (await mockRemoteDataSource.get(response.pokemons!.first.name)).name); 56 | }); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /test/data/datasources/pokemon_local_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:pokedex/data/datasources/pokemon_local_datasource.dart'; 5 | import 'package:pokedex/domain/entities/pokemon.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | import 'pokemon_local_datasource_test.mocks.dart'; 9 | 10 | @GenerateMocks([SharedPreferences]) 11 | void main() { 12 | late MockSharedPreferences mockSharedPreferences; 13 | late PokemonLocalDatasourceImpl localDatasource; 14 | String? favorites; 15 | 16 | setUp(() { 17 | mockSharedPreferences = MockSharedPreferences(); 18 | localDatasource = PokemonLocalDatasourceImpl(mockSharedPreferences); 19 | when(mockSharedPreferences.getString('favourites')) 20 | .thenAnswer((_) => favorites); 21 | when(mockSharedPreferences.setString('favourites', any)) 22 | .thenAnswer((invocation) async { 23 | favorites = invocation.positionalArguments[1]; 24 | return true; 25 | }); 26 | }); 27 | group('pokemonLocalDataSource', () { 28 | test('should be able to retrieve saved favorited pokemon', () async { 29 | favorites = null; 30 | final pokemon = Pokemon(id: 1, name: 'fave'); 31 | 32 | await localDatasource.addFavoritePokemon(pokemon); 33 | 34 | final savedPokemons = await localDatasource.getFavoritePokemons(); 35 | 36 | expect(pokemon, savedPokemons.pokemons!.first); 37 | }); 38 | }); 39 | 40 | test('should no longer return deleted favourite', () async { 41 | favorites = '[{"id":1,"name":"fave"}]'; 42 | 43 | await localDatasource.removeFavoritePokemon(Pokemon(id: 1, name: 'fave')); 44 | 45 | final savedPokemons = await localDatasource.getFavoritePokemons(); 46 | 47 | expect(savedPokemons.pokemons!.length, 0); 48 | }); 49 | 50 | test('should return latest data after favorite is updated', () async { 51 | favorites = '[{"id":1,"name":"fave"}]'; 52 | 53 | await localDatasource 54 | .updateFavoritePokemon(Pokemon(id: 1, name: 'updated_fave')); 55 | 56 | final savedPokemons = await localDatasource.getFavoritePokemons(); 57 | 58 | expect(savedPokemons.pokemons!.first.name, 'updated_fave'); 59 | }); 60 | 61 | test('isFavoritePokemon should be true if pokemon is in favorites', () async { 62 | favorites = '[{"id":1,"name":"fave"}]'; 63 | 64 | final isFave = 65 | await localDatasource.isFavoritePokemon(Pokemon(id: 1, name: 'fave')); 66 | 67 | expect(isFave, isTrue); 68 | }); 69 | 70 | test('isFavoritePokemon should be false if pokemon is not in favorites', 71 | () async { 72 | favorites = null; 73 | 74 | final isFave = 75 | await localDatasource.isFavoritePokemon(Pokemon(id: 1, name: 'fave')); 76 | 77 | expect(isFave, isFalse); 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /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/data/datasources/pokemon_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:pokedex/domain/entities/pokemon.dart'; 2 | import 'package:pokedex/domain/repositories/pokemon_repository.dart'; 3 | import 'dart:convert'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | abstract class PokemonLocalDatasource extends PokemonRepository {} 7 | 8 | class PokemonLocalDatasourceImpl extends PokemonLocalDatasource { 9 | final SharedPreferences sharedPreferences; 10 | PokemonLocalDatasourceImpl(this.sharedPreferences); 11 | @override 12 | Future addFavoritePokemon(Pokemon pokemon) async { 13 | final favoritesJson = sharedPreferences.getString('favourites'); 14 | if (favoritesJson == null) { 15 | final json = jsonEncode([pokemon.toJson()]); 16 | await sharedPreferences.setString('favourites', json); 17 | } else { 18 | final json = jsonEncode([...jsonDecode(favoritesJson), pokemon.toJson()]); 19 | await sharedPreferences.setString('favourites', json); 20 | } 21 | } 22 | 23 | @override 24 | Future isFavoritePokemon(Pokemon pokemon) async { 25 | final favoritesJson = sharedPreferences.getString('favourites'); 26 | if (favoritesJson == null) { 27 | return false; 28 | } 29 | final favorites = jsonDecode(favoritesJson); 30 | final favorite = 31 | favorites.firstWhere((f) => f['id'] == pokemon.id, orElse: () => null); 32 | return favorite != null; 33 | } 34 | 35 | @override 36 | Future updateFavoritePokemon(Pokemon pokemon) async { 37 | final favoritesJson = sharedPreferences.getString('favourites'); 38 | if (favoritesJson != null) { 39 | final favorites = jsonDecode(favoritesJson); 40 | final index = favorites.indexWhere((p) => p['id'] == pokemon.id); 41 | favorites[index] = pokemon.toJson(); 42 | final json = jsonEncode(favorites); 43 | sharedPreferences.setString('favourites', json); 44 | } 45 | } 46 | 47 | @override 48 | Future removeFavoritePokemon(Pokemon pokemon) async { 49 | final favoritesJson = sharedPreferences.getString('favourites'); 50 | if (favoritesJson != null) { 51 | final favorites = jsonDecode(favoritesJson); 52 | final index = 53 | favorites.indexWhere((element) => element['id'] == pokemon.id); 54 | favorites.removeAt(index); 55 | final json = jsonEncode(favorites); 56 | sharedPreferences.setString('favourites', json); 57 | } 58 | } 59 | 60 | @override 61 | Future getFavoritePokemons( 62 | {bool? invalidateCache}) async { 63 | final favoritesJson = sharedPreferences.getString('favourites'); 64 | if (favoritesJson == null) { 65 | return GetFavoritePokemonResponse([], true); 66 | } 67 | final json = jsonDecode(favoritesJson); 68 | final List pokemons = []; 69 | for (var pokemon in json) { 70 | pokemons.add(Pokemon.fromJson(pokemon)); 71 | } 72 | return GetFavoritePokemonResponse(pokemons, true); 73 | } 74 | 75 | @override 76 | Future> getPokemons({int offset = 0, int limit = 20}) { 77 | throw UnimplementedError(); 78 | } 79 | 80 | @override 81 | Future get(nameOrId) { 82 | throw UnimplementedError(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /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 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/presentation/widgets/pokemon_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:palette_generator/palette_generator.dart'; 4 | import 'package:pokedex/domain/entities/pokemon.dart'; 5 | import 'package:pokedex/presentation/core/app_colors.dart'; 6 | import 'package:pokedex/presentation/util.dart'; 7 | 8 | class PokemonCard extends StatefulWidget { 9 | final Pokemon? pokemon; 10 | final Function? onTap; 11 | 12 | const PokemonCard({Key? key, this.pokemon, this.onTap}) : super(key: key); 13 | 14 | @override 15 | State createState() => _PokemonCardState(); 16 | } 17 | 18 | class _PokemonCardState extends State { 19 | Color? cardColor; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | WidgetsBinding.instance?.addPostFrameCallback((_) async { 25 | cardColor = 26 | await computeDominatImageColor(widget.pokemon?.imageUrl ?? ''); 27 | setState(() {}); 28 | }); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return LayoutBuilder(builder: (context, constraints) { 34 | return InkWell( 35 | onTap: () => widget.onTap?.call(), 36 | child: AnimatedContainer( 37 | duration: const Duration(milliseconds: 300), 38 | width: constraints.maxWidth, 39 | height: constraints.maxHeight, 40 | color: Colors.white, 41 | child: Column(children: [ 42 | Container( 43 | color: cardColor ?? Colors.grey[200], 44 | width: constraints.maxWidth, 45 | height: constraints.maxHeight * 0.6, 46 | child: CachedNetworkImage( 47 | imageUrl: widget.pokemon?.imageUrl ?? '', 48 | fit: BoxFit.cover, 49 | errorWidget: (context, url, error) => Container( 50 | color: Colors.grey[200], 51 | ), 52 | ), 53 | ), 54 | Expanded( 55 | child: Container( 56 | padding: const EdgeInsets.all(10), 57 | width: double.infinity, 58 | child: Column( 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | mainAxisSize: MainAxisSize.max, 61 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 62 | children: [ 63 | Column( 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | Text( 67 | "#${widget.pokemon?.id?.toString().padLeft(3, '0') ?? ''}", 68 | style: const TextStyle( 69 | color: AppColors.grey, 70 | fontWeight: FontWeight.w400, 71 | fontSize: 12), 72 | ), 73 | const SizedBox(height: 5), 74 | Text(widget.pokemon?.name ?? '', 75 | style: const TextStyle( 76 | color: AppColors.black, 77 | fontWeight: FontWeight.w600, 78 | fontSize: 14)), 79 | ], 80 | ), 81 | Text( 82 | widget.pokemon?.types?.map((e) => e.name).join(', ') ?? 83 | '', 84 | maxLines: 1, 85 | overflow: TextOverflow.ellipsis, 86 | style: const TextStyle( 87 | color: AppColors.grey, 88 | fontWeight: FontWeight.w400, 89 | fontSize: 12)), 90 | ], 91 | ), 92 | )) 93 | ])), 94 | ); 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pokedex 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.15.1 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | flutter_bloc: ^8.0.1 38 | bloc: ^8.0.2 39 | retrofit: ^3.0.1 40 | dio: ^4.0.4 41 | freezed_annotation: ^1.1.0 42 | json_annotation: ^4.4.0 43 | get_it: ^7.2.0 44 | flutter_svg: ^1.0.0 45 | cached_network_image: ^3.2.0 46 | palette_generator: ^0.3.2 47 | shared_preferences: ^2.0.12 48 | responsive_builder: ^0.4.1 49 | 50 | dev_dependencies: 51 | flutter_test: 52 | sdk: flutter 53 | 54 | # The "flutter_lints" package below contains a set of recommended lints to 55 | # encourage good coding practices. The lint set provided by the package is 56 | # activated in the `analysis_options.yaml` file located at the root of your 57 | # package. See that file for information about deactivating specific lint 58 | # rules and activating additional ones. 59 | flutter_lints: ^1.0.0 60 | retrofit_generator: ^3.0.1+1 61 | build_runner: ^2.1.7 62 | build_test: ^2.1.5 63 | json_serializable: ^6.1.3 64 | freezed: ^1.1.0 65 | flutter_native_splash: ^1.3.3 66 | mockito: ^5.0.17 67 | 68 | # For information on the generic Dart part of this file, see the 69 | # following page: https://dart.dev/tools/pub/pubspec 70 | 71 | # The following section is specific to Flutter. 72 | flutter: 73 | 74 | # The following line ensures that the Material Icons font is 75 | # included with your application, so that you can use the icons in 76 | # the material Icons class. 77 | uses-material-design: true 78 | 79 | # To add assets to your application, add an assets section, like this: 80 | assets: 81 | - images/pokemon.svg 82 | - images/temp.svg 83 | 84 | # An image asset can refer to one or more resolution-specific "variants", see 85 | # https://flutter.dev/assets-and-images/#resolution-aware. 86 | 87 | # For details regarding adding assets from package dependencies, see 88 | # https://flutter.dev/assets-and-images/#from-packages 89 | 90 | # To add custom fonts to your application, add a fonts section here, 91 | # in this "flutter" section. Each entry in this list should have a 92 | # "family" key with the font family name, and a "fonts" key with a 93 | # list giving the asset and other descriptors for the font. For 94 | # example: 95 | # fonts: 96 | # - family: Schyler 97 | # fonts: 98 | # - asset: fonts/Schyler-Regular.ttf 99 | # - asset: fonts/Schyler-Italic.ttf 100 | # style: italic 101 | # - family: Trajan Pro 102 | # fonts: 103 | # - asset: fonts/TrajanPro.ttf 104 | # - asset: fonts/TrajanPro_Bold.ttf 105 | # weight: 700 106 | # 107 | # For details regarding fonts from package dependencies, 108 | # see https://flutter.dev/custom-fonts/#from-packages 109 | 110 | flutter_native_splash: 111 | color: "#3558CD" 112 | image: images/splash.png 113 | branding_mode: bottom 114 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | pokedex 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 109 | 110 | -------------------------------------------------------------------------------- /lib/presentation/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_svg/svg.dart'; 5 | import 'package:pokedex/domain/entities/pokemon.dart'; 6 | import 'package:pokedex/presentation/core/app_colors.dart'; 7 | import 'package:pokedex/presentation/cubit/favorite_pokemon_cubit.dart'; 8 | import 'package:pokedex/presentation/cubit/pokemon_cubit.dart'; 9 | import 'package:pokedex/presentation/widgets/pokemon_card.dart'; 10 | import 'package:responsive_builder/responsive_builder.dart'; 11 | 12 | class HomePage extends StatefulWidget { 13 | const HomePage({Key? key}) : super(key: key); 14 | 15 | @override 16 | _HomePageState createState() => _HomePageState(); 17 | } 18 | 19 | class _HomePageState extends State 20 | with SingleTickerProviderStateMixin { 21 | late ScrollController _scrollController; 22 | @override 23 | void initState() { 24 | super.initState(); 25 | //load pokemons 26 | context.read().loadPokemons(); 27 | 28 | //setup pagination scroll listener 29 | _scrollController = ScrollController(); 30 | _scrollController.addListener(() { 31 | if (_scrollController.position.pixels == 32 | _scrollController.position.maxScrollExtent) { 33 | final currentState = context.read().state; 34 | if (currentState is PokemonLoadedState) { 35 | context 36 | .read() 37 | .loadPokemons(loadedPokemons: currentState.pokemons); 38 | } 39 | } 40 | }); 41 | 42 | //fetch favorites 43 | context.read().loadFavorites(invalidateCache: true); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return DefaultTabController( 49 | length: 2, 50 | child: Scaffold( 51 | body: SafeArea( 52 | child: Container( 53 | color: Colors.white, 54 | width: double.infinity, 55 | child: Column( 56 | mainAxisSize: MainAxisSize.max, 57 | children: [ 58 | const SizedBox( 59 | height: 20, 60 | ), 61 | Row( 62 | mainAxisSize: MainAxisSize.max, 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | children: [ 65 | SvgPicture.asset('images/pokemon.svg'), 66 | const SizedBox(width: 20), 67 | const Text( 68 | 'Pokedex', 69 | style: TextStyle( 70 | fontSize: 30, 71 | fontWeight: FontWeight.bold, 72 | ), 73 | ), 74 | ], 75 | ), 76 | TabBar(indicatorColor: AppColors.primaryColor, tabs: [ 77 | const Tab( 78 | child: Text( 79 | 'All Pokemons', 80 | style: TextStyle( 81 | fontSize: 16, 82 | fontWeight: FontWeight.w500, 83 | color: Colors.black, 84 | ), 85 | ), 86 | ), 87 | Tab( 88 | child: BlocConsumer( 90 | builder: (context, state) { 91 | return Row( 92 | children: [ 93 | const Text( 94 | 'Favorite Pokemons', 95 | style: TextStyle( 96 | fontSize: 16, 97 | fontWeight: FontWeight.w500, 98 | color: Colors.black, 99 | ), 100 | ), 101 | if (state is FavoritePokemonLoadedState) 102 | const SizedBox(width: 10), 103 | if (state is FavoritePokemonLoadedState && 104 | state.pokemon!.isNotEmpty) 105 | Container( 106 | width: 20, 107 | height: 20, 108 | decoration: BoxDecoration( 109 | color: AppColors.lightBlue, 110 | borderRadius: BorderRadius.circular(10), 111 | ), 112 | child: Center( 113 | child: Text( 114 | state.pokemon!.length.toString(), 115 | style: const TextStyle( 116 | fontSize: 12, 117 | fontWeight: FontWeight.bold, 118 | color: Colors.white, 119 | ), 120 | ), 121 | ), 122 | ), 123 | ], 124 | ); 125 | }, 126 | listener: (context, state) {}), 127 | ), 128 | ]), 129 | Expanded( 130 | child: TabBarView(children: [ 131 | Container( 132 | color: Colors.grey[100], 133 | child: Padding( 134 | padding: const EdgeInsets.all(10), 135 | child: BlocConsumer( 136 | listener: (context, state) {}, 137 | builder: (context, state) { 138 | if (state is PokemonLoadingState || 139 | state is PokemonInitialState) { 140 | return const Center( 141 | child: CircularProgressIndicator(), 142 | ); 143 | } else if (state is PokemonLoadedState || 144 | state is PokemonLoadingMoreState) { 145 | return Column(children: [ 146 | Expanded( 147 | child: buildLoadedPokemons( 148 | pokemons: (state as dynamic).pokemons, 149 | scrollController: _scrollController, 150 | onRefresh: () async { 151 | await context 152 | .read() 153 | .loadPokemons(); 154 | })), 155 | if (state is PokemonLoadingMoreState) 156 | const Center( 157 | child: Padding( 158 | padding: EdgeInsets.all(10), 159 | child: CircularProgressIndicator(), 160 | ), 161 | ), 162 | ]); 163 | } 164 | 165 | //error state 166 | return Column( 167 | mainAxisAlignment: MainAxisAlignment.center, 168 | children: [ 169 | Text( 170 | (state as PokemonErrorState).message, 171 | style: const TextStyle( 172 | fontSize: 20, 173 | fontWeight: FontWeight.bold, 174 | ), 175 | ), 176 | const SizedBox(height: 10), 177 | ElevatedButton( 178 | onPressed: () { 179 | context.read().loadPokemons(); 180 | }, 181 | child: const Text('Retry'), 182 | ) 183 | ], 184 | ); 185 | }, 186 | ), 187 | ), 188 | ), 189 | Container( 190 | color: Colors.grey[100], 191 | child: Padding( 192 | padding: const EdgeInsets.all(10), 193 | child: BlocConsumer( 195 | listener: (context, state) { 196 | if (state is FavoritePokemonLoadedState && 197 | state.isCachedData == true && 198 | ModalRoute.of(context)!.isCurrent) { 199 | ScaffoldMessenger.of(context) 200 | .showSnackBar(const SnackBar( 201 | content: Text('No internet connection'), 202 | duration: Duration(seconds: 2), 203 | )); 204 | } 205 | }, 206 | builder: (context, state) { 207 | if (state is FavoritePokemonLoadingState || 208 | state is FavoritePokemonInitialState) { 209 | return const Center( 210 | child: CircularProgressIndicator(), 211 | ); 212 | } else if (state is FavoritePokemonLoadedState) { 213 | if (state.pokemon!.isEmpty) { 214 | return const Center( 215 | child: Text('No favorites'), 216 | ); 217 | } 218 | 219 | return Column(children: [ 220 | Expanded( 221 | child: buildLoadedPokemons( 222 | pokemons: state.pokemon!, 223 | onRefresh: () async { 224 | await context 225 | .read() 226 | .loadFavorites(invalidateCache: true); 227 | }, 228 | )), 229 | if (state is PokemonLoadingMoreState) 230 | const Center( 231 | child: Padding( 232 | padding: EdgeInsets.all(10), 233 | child: CircularProgressIndicator(), 234 | ), 235 | ), 236 | ]); 237 | } 238 | 239 | return Container(); 240 | }, 241 | ), 242 | ), 243 | ) 244 | ])) 245 | ], 246 | ), 247 | )), 248 | )); 249 | } 250 | 251 | Widget buildLoadedPokemons( 252 | {required List pokemons, 253 | ScrollController? scrollController, 254 | AsyncCallback? onRefresh}) { 255 | return OrientationBuilder(builder: (context, orientation) { 256 | return RefreshIndicator( 257 | child: GridView.builder( 258 | itemCount: pokemons.length, 259 | controller: scrollController, 260 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 261 | childAspectRatio: 0.6, 262 | crossAxisCount: getValueForScreenType( 263 | context: context, 264 | mobile: orientation == Orientation.portrait ? 3 : 4, 265 | tablet: 5, 266 | desktop: 7, 267 | ), 268 | mainAxisSpacing: 10, 269 | crossAxisSpacing: 10), 270 | itemBuilder: (context, count) { 271 | return PokemonCard( 272 | pokemon: pokemons[count], 273 | onTap: () { 274 | Navigator.pushNamed(context, '/details', 275 | arguments: pokemons[count]); 276 | }, 277 | ); 278 | }, 279 | ), 280 | onRefresh: () async { 281 | await onRefresh?.call(); 282 | }); 283 | }); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /lib/presentation/pages/pokemon_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:pokedex/domain/entities/pokemon.dart'; 5 | import 'package:pokedex/injection_container.dart'; 6 | import 'package:pokedex/presentation/core/app_colors.dart'; 7 | import 'package:pokedex/presentation/cubit/favorite_pokemon_cubit.dart'; 8 | import 'package:pokedex/presentation/widgets/pokemon_attribute_card.dart'; 9 | import 'package:pokedex/presentation/widgets/pokemon_stat.dart'; 10 | 11 | import '../util.dart'; 12 | 13 | class PokemonDetailsPage extends StatefulWidget { 14 | final Pokemon? pokemon; 15 | const PokemonDetailsPage({Key? key, this.pokemon}) : super(key: key); 16 | 17 | @override 18 | _PokemonDetailsPageState createState() => _PokemonDetailsPageState(); 19 | } 20 | 21 | class _PokemonDetailsPageState extends State { 22 | Pokemon? _pokemon; 23 | Color? dominantColor; 24 | double titleOpacity = 0; 25 | bool? isFavorite; 26 | FavoritePokemonCubit? cubit; 27 | 28 | final ScrollController _scrollController = ScrollController(); 29 | @override 30 | void initState() { 31 | super.initState(); 32 | _pokemon = widget.pokemon; 33 | 34 | cubit = context.read(); 35 | 36 | //scroll lister to change the opacity of sliverappbar title upon expansion 37 | _scrollController.addListener(() { 38 | final calculatedOpacity = (_scrollController.position.pixels >= 250 39 | ? 250 40 | : _scrollController.position.pixels) / 41 | 250; 42 | titleOpacity = calculatedOpacity < 0 ? 0 : calculatedOpacity; 43 | setState(() {}); 44 | }); 45 | 46 | WidgetsBinding.instance?.addPostFrameCallback((_) async { 47 | //get pokemon from route if not provided 48 | _pokemon ??= ModalRoute.of(context)?.settings.arguments as Pokemon; 49 | 50 | //compute dominant color 51 | dominantColor = await computeDominatImageColor(_pokemon?.imageUrl ?? ''); 52 | 53 | //check if pokemon is favorite 54 | checkFavorite(); 55 | }); 56 | } 57 | 58 | Future checkFavorite() async { 59 | isFavorite = await cubit!.isFavorite(_pokemon!); 60 | setState(() {}); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | floatingActionButton: 67 | BlocConsumer( 68 | builder: (context, state) { 69 | return SizedBox( 70 | height: 50, 71 | width: 200, 72 | child: ElevatedButton( 73 | onPressed: () async { 74 | if (state is FavoritePokemonLoadingState) { 75 | return; 76 | } 77 | 78 | try { 79 | if (isFavorite == false) { 80 | await cubit!.addToFavorites(_pokemon!); 81 | } else if (isFavorite == true) { 82 | await cubit!.removeFromFavorites(_pokemon!); 83 | } 84 | // ignore: empty_catches 85 | } catch (err) {} 86 | }, 87 | style: ElevatedButton.styleFrom( 88 | textStyle: const TextStyle(color: Colors.white), 89 | shape: RoundedRectangleBorder( 90 | borderRadius: BorderRadius.circular(30), 91 | ), 92 | primary: isFavorite == true 93 | ? AppColors.lightBlueAccent 94 | : AppColors.lightBlue), 95 | child: state is FavoritePokemonLoadingState || isFavorite == null 96 | ? const Center( 97 | child: SizedBox( 98 | height: 25, 99 | width: 25, 100 | child: CircularProgressIndicator(), 101 | )) 102 | : Padding( 103 | child: Text( 104 | isFavorite == true 105 | ? 'Remove from favorites' 106 | : 'Add to favorites', 107 | style: TextStyle( 108 | fontSize: 14, 109 | fontWeight: FontWeight.w700, 110 | color: isFavorite == true 111 | ? AppColors.lightBlue 112 | : Colors.white), 113 | ), 114 | padding: const EdgeInsets.only( 115 | left: 10, right: 10, top: 15, bottom: 15), 116 | ), 117 | ), 118 | ); 119 | }, listener: (context, state) async { 120 | if (state is FavoritePokemonLoadedState) { 121 | await checkFavorite(); 122 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 123 | content: Text(isFavorite == true 124 | ? 'Added to favorites' 125 | : 'Removed from favorites'), 126 | duration: const Duration(seconds: 2), 127 | )); 128 | } 129 | 130 | if (state is FavoritePokemonErrorState) { 131 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 132 | content: Text(state.message), 133 | duration: const Duration(seconds: 2), 134 | )); 135 | } 136 | }), 137 | body: NestedScrollView( 138 | controller: _scrollController, 139 | headerSliverBuilder: (context, innerBoxScrolled) { 140 | return [ 141 | SliverAppBar( 142 | expandedHeight: 230, 143 | floating: false, 144 | pinned: true, 145 | leading: GestureDetector( 146 | onTap: () => Navigator.pop(context), 147 | child: const Icon( 148 | Icons.arrow_back_ios, 149 | color: Colors.black, 150 | ), 151 | ), 152 | backgroundColor: dominantColor ?? Colors.grey[200]!, 153 | flexibleSpace: FlexibleSpaceBar( 154 | title: Opacity( 155 | opacity: titleOpacity, 156 | child: Text( 157 | _pokemon?.name?.capitalizeFirstLetter() ?? '', 158 | style: const TextStyle( 159 | fontSize: 20, 160 | fontWeight: FontWeight.bold, 161 | color: AppColors.darkBlue), 162 | ), 163 | ), 164 | centerTitle: false, 165 | background: Stack( 166 | children: [ 167 | Container( 168 | height: double.infinity, 169 | width: double.infinity, 170 | decoration: BoxDecoration( 171 | gradient: LinearGradient( 172 | begin: Alignment.topCenter, 173 | end: Alignment.bottomCenter, 174 | colors: [ 175 | dominantColor?.withOpacity(0.5) ?? 176 | Colors.grey[200]!, 177 | dominantColor?.withOpacity(0.8) ?? 178 | Colors.grey[100]!, 179 | ], 180 | ), 181 | ), 182 | child: Align( 183 | alignment: Alignment.bottomRight, 184 | child: SizedBox( 185 | height: 150, 186 | child: CachedNetworkImage( 187 | imageUrl: _pokemon?.imageUrl ?? '', 188 | fit: BoxFit.cover, 189 | errorWidget: (context, url, error) => Container( 190 | color: Colors.grey[200], 191 | ), 192 | ), 193 | ), 194 | ), 195 | ), 196 | Container( 197 | padding: const EdgeInsets.all(10), 198 | height: double.infinity, 199 | width: double.infinity, 200 | child: Column( 201 | crossAxisAlignment: CrossAxisAlignment.start, 202 | children: [ 203 | const SizedBox( 204 | height: kToolbarHeight * 1.3, 205 | ), 206 | Text(_pokemon?.name?.capitalizeFirstLetter() ?? '', 207 | style: const TextStyle( 208 | fontSize: 30, 209 | fontWeight: FontWeight.bold, 210 | color: AppColors.darkBlue)), 211 | const SizedBox( 212 | height: 5, 213 | ), 214 | Text( 215 | _pokemon?.types?.map((e) => e.name).join(', ') ?? 216 | '', 217 | style: const TextStyle( 218 | fontSize: 16, color: AppColors.darkBlue)), 219 | Expanded( 220 | child: Align( 221 | alignment: Alignment.bottomLeft, 222 | child: Text( 223 | "#${_pokemon?.id?.toString().padLeft(3, '0') ?? ''}", 224 | style: const TextStyle( 225 | fontSize: 15, 226 | color: AppColors.darkBlue)))) 227 | ], 228 | ), 229 | ), 230 | ], 231 | ), 232 | ), 233 | ) 234 | ]; 235 | }, 236 | body: Column(children: [ 237 | Container( 238 | padding: const EdgeInsets.all(20), 239 | child: Row( 240 | crossAxisAlignment: CrossAxisAlignment.center, 241 | children: [ 242 | PokemonAttributeCard( 243 | title: 'Height', 244 | value: _pokemon?.height?.toString() ?? '', 245 | ), 246 | const SizedBox(width: 30), 247 | PokemonAttributeCard( 248 | title: 'Weight', 249 | value: _pokemon?.weight?.toString() ?? '', 250 | ), 251 | const SizedBox(width: 30), 252 | PokemonAttributeCard( 253 | title: 'BMI', 254 | value: _pokemon?.getBMI().toStringAsPrecision(3) ?? '0', 255 | ), 256 | ], 257 | ), 258 | ), 259 | Container( 260 | height: 10, 261 | width: double.infinity, 262 | color: Colors.grey[200], 263 | ), 264 | Expanded( 265 | child: Container( 266 | width: double.infinity, 267 | color: Colors.white, 268 | child: ListView(padding: EdgeInsets.zero, children: [ 269 | const Padding( 270 | padding: EdgeInsets.all(20), 271 | child: Text('Base Stats', 272 | style: TextStyle( 273 | fontSize: 20, 274 | fontWeight: FontWeight.bold, 275 | color: AppColors.darkBlue)), 276 | ), 277 | Divider( 278 | thickness: 2, 279 | color: Colors.grey[200], 280 | ), 281 | const SizedBox( 282 | height: 10, 283 | ), 284 | ..._pokemon?.stats?.map((e) => Padding( 285 | padding: const EdgeInsets.only( 286 | bottom: 20, left: 20, right: 20), 287 | child: Column( 288 | children: [ 289 | PokemonStat(title: e.name, value: e.baseStat), 290 | const SizedBox(height: 10), 291 | ], 292 | ), 293 | )) ?? 294 | const [], 295 | Padding( 296 | padding: const EdgeInsets.only( 297 | bottom: 20, left: 20, right: 20), 298 | child: Column( 299 | children: [ 300 | PokemonStat( 301 | title: 'Avg. Power', 302 | value: _pokemon?.averagePower().toInt()), 303 | const SizedBox(height: 10), 304 | ], 305 | ), 306 | ) 307 | ]))) 308 | ]), 309 | ), 310 | ); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 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 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1300; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 300 | SWIFT_VERSION = 5.0; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | }; 303 | name = Profile; 304 | }; 305 | 97C147031CF9000F007C117D /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = dwarf; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | ENABLE_TESTABILITY = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_OPTIMIZATION_LEVEL = 0; 342 | GCC_PREPROCESSOR_DEFINITIONS = ( 343 | "DEBUG=1", 344 | "$(inherited)", 345 | ); 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | }; 358 | name = Debug; 359 | }; 360 | 97C147041CF9000F007C117D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SUPPORTED_PLATFORMS = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 97C147061CF9000F007C117D /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | CLANG_ENABLE_MODULES = YES; 418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 419 | ENABLE_BITCODE = NO; 420 | INFOPLIST_FILE = Runner/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 429 | SWIFT_VERSION = 5.0; 430 | VERSIONING_SYSTEM = "apple-generic"; 431 | }; 432 | name = Debug; 433 | }; 434 | 97C147071CF9000F007C117D /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | CLANG_ENABLE_MODULES = YES; 440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 441 | ENABLE_BITCODE = NO; 442 | INFOPLIST_FILE = Runner/Info.plist; 443 | LD_RUNPATH_SEARCH_PATHS = ( 444 | "$(inherited)", 445 | "@executable_path/Frameworks", 446 | ); 447 | PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 450 | SWIFT_VERSION = 5.0; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147031CF9000F007C117D /* Debug */, 462 | 97C147041CF9000F007C117D /* Release */, 463 | 249021D3217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 97C147061CF9000F007C117D /* Debug */, 472 | 97C147071CF9000F007C117D /* Release */, 473 | 249021D4217E4FDB00AE95B9 /* Profile */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "31.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.8.0" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "3.1.8" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.3.0" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.8.2" 39 | bloc: 40 | dependency: "direct main" 41 | description: 42 | name: bloc 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "8.0.2" 46 | boolean_selector: 47 | dependency: transitive 48 | description: 49 | name: boolean_selector 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.0" 53 | build: 54 | dependency: transitive 55 | description: 56 | name: build 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.2.1" 60 | build_config: 61 | dependency: transitive 62 | description: 63 | name: build_config 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.0" 67 | build_daemon: 68 | dependency: transitive 69 | description: 70 | name: build_daemon 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "3.0.1" 74 | build_resolvers: 75 | dependency: transitive 76 | description: 77 | name: build_resolvers 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.0.6" 81 | build_runner: 82 | dependency: "direct dev" 83 | description: 84 | name: build_runner 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.1.7" 88 | build_runner_core: 89 | dependency: transitive 90 | description: 91 | name: build_runner_core 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "7.2.3" 95 | build_test: 96 | dependency: "direct dev" 97 | description: 98 | name: build_test 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.1.5" 102 | built_collection: 103 | dependency: transitive 104 | description: 105 | name: built_collection 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "5.1.1" 109 | built_value: 110 | dependency: transitive 111 | description: 112 | name: built_value 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "8.1.3" 116 | cached_network_image: 117 | dependency: "direct main" 118 | description: 119 | name: cached_network_image 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "3.2.0" 123 | cached_network_image_platform_interface: 124 | dependency: transitive 125 | description: 126 | name: cached_network_image_platform_interface 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.0.0" 130 | cached_network_image_web: 131 | dependency: transitive 132 | description: 133 | name: cached_network_image_web 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.0.1" 137 | characters: 138 | dependency: transitive 139 | description: 140 | name: characters 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.2.0" 144 | charcode: 145 | dependency: transitive 146 | description: 147 | name: charcode 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.3.1" 151 | checked_yaml: 152 | dependency: transitive 153 | description: 154 | name: checked_yaml 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.0.1" 158 | cli_util: 159 | dependency: transitive 160 | description: 161 | name: cli_util 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.3.5" 165 | clock: 166 | dependency: transitive 167 | description: 168 | name: clock 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.1.0" 172 | code_builder: 173 | dependency: transitive 174 | description: 175 | name: code_builder 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "4.1.0" 179 | collection: 180 | dependency: transitive 181 | description: 182 | name: collection 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.15.0" 186 | convert: 187 | dependency: transitive 188 | description: 189 | name: convert 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "3.0.1" 193 | coverage: 194 | dependency: transitive 195 | description: 196 | name: coverage 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.0.3" 200 | crypto: 201 | dependency: transitive 202 | description: 203 | name: crypto 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "3.0.1" 207 | csslib: 208 | dependency: transitive 209 | description: 210 | name: csslib 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "0.17.1" 214 | cupertino_icons: 215 | dependency: "direct main" 216 | description: 217 | name: cupertino_icons 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.0.4" 221 | dart_style: 222 | dependency: transitive 223 | description: 224 | name: dart_style 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "2.2.1" 228 | dio: 229 | dependency: "direct main" 230 | description: 231 | name: dio 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "4.0.4" 235 | fake_async: 236 | dependency: transitive 237 | description: 238 | name: fake_async 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.2.0" 242 | ffi: 243 | dependency: transitive 244 | description: 245 | name: ffi 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.1.2" 249 | file: 250 | dependency: transitive 251 | description: 252 | name: file 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "6.1.2" 256 | fixnum: 257 | dependency: transitive 258 | description: 259 | name: fixnum 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.0.0" 263 | flutter: 264 | dependency: "direct main" 265 | description: flutter 266 | source: sdk 267 | version: "0.0.0" 268 | flutter_bloc: 269 | dependency: "direct main" 270 | description: 271 | name: flutter_bloc 272 | url: "https://pub.dartlang.org" 273 | source: hosted 274 | version: "8.0.1" 275 | flutter_blurhash: 276 | dependency: transitive 277 | description: 278 | name: flutter_blurhash 279 | url: "https://pub.dartlang.org" 280 | source: hosted 281 | version: "0.6.0" 282 | flutter_cache_manager: 283 | dependency: transitive 284 | description: 285 | name: flutter_cache_manager 286 | url: "https://pub.dartlang.org" 287 | source: hosted 288 | version: "3.3.0" 289 | flutter_lints: 290 | dependency: "direct dev" 291 | description: 292 | name: flutter_lints 293 | url: "https://pub.dartlang.org" 294 | source: hosted 295 | version: "1.0.4" 296 | flutter_native_splash: 297 | dependency: "direct dev" 298 | description: 299 | name: flutter_native_splash 300 | url: "https://pub.dartlang.org" 301 | source: hosted 302 | version: "1.3.3" 303 | flutter_svg: 304 | dependency: "direct main" 305 | description: 306 | name: flutter_svg 307 | url: "https://pub.dartlang.org" 308 | source: hosted 309 | version: "1.0.0" 310 | flutter_test: 311 | dependency: "direct dev" 312 | description: flutter 313 | source: sdk 314 | version: "0.0.0" 315 | flutter_web_plugins: 316 | dependency: transitive 317 | description: flutter 318 | source: sdk 319 | version: "0.0.0" 320 | freezed: 321 | dependency: "direct dev" 322 | description: 323 | name: freezed 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "1.1.0" 327 | freezed_annotation: 328 | dependency: "direct main" 329 | description: 330 | name: freezed_annotation 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.1.0" 334 | frontend_server_client: 335 | dependency: transitive 336 | description: 337 | name: frontend_server_client 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.1.2" 341 | get_it: 342 | dependency: "direct main" 343 | description: 344 | name: get_it 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "7.2.0" 348 | glob: 349 | dependency: transitive 350 | description: 351 | name: glob 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "2.0.2" 355 | graphs: 356 | dependency: transitive 357 | description: 358 | name: graphs 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "2.1.0" 362 | html: 363 | dependency: transitive 364 | description: 365 | name: html 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "0.15.0" 369 | http: 370 | dependency: transitive 371 | description: 372 | name: http 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "0.13.4" 376 | http_multi_server: 377 | dependency: transitive 378 | description: 379 | name: http_multi_server 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "3.0.1" 383 | http_parser: 384 | dependency: transitive 385 | description: 386 | name: http_parser 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "4.0.0" 390 | image: 391 | dependency: transitive 392 | description: 393 | name: image 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "3.1.0" 397 | io: 398 | dependency: transitive 399 | description: 400 | name: io 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "1.0.3" 404 | js: 405 | dependency: transitive 406 | description: 407 | name: js 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "0.6.3" 411 | json_annotation: 412 | dependency: "direct main" 413 | description: 414 | name: json_annotation 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "4.4.0" 418 | json_serializable: 419 | dependency: "direct dev" 420 | description: 421 | name: json_serializable 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "6.1.3" 425 | lints: 426 | dependency: transitive 427 | description: 428 | name: lints 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.0.1" 432 | logging: 433 | dependency: transitive 434 | description: 435 | name: logging 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.0.2" 439 | matcher: 440 | dependency: transitive 441 | description: 442 | name: matcher 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "0.12.11" 446 | meta: 447 | dependency: transitive 448 | description: 449 | name: meta 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "1.7.0" 453 | mime: 454 | dependency: transitive 455 | description: 456 | name: mime 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "1.0.1" 460 | mockito: 461 | dependency: "direct dev" 462 | description: 463 | name: mockito 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "5.0.17" 467 | nested: 468 | dependency: transitive 469 | description: 470 | name: nested 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "1.0.0" 474 | node_preamble: 475 | dependency: transitive 476 | description: 477 | name: node_preamble 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "2.0.1" 481 | octo_image: 482 | dependency: transitive 483 | description: 484 | name: octo_image 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "1.0.1" 488 | package_config: 489 | dependency: transitive 490 | description: 491 | name: package_config 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "2.0.2" 495 | palette_generator: 496 | dependency: "direct main" 497 | description: 498 | name: palette_generator 499 | url: "https://pub.dartlang.org" 500 | source: hosted 501 | version: "0.3.2" 502 | path: 503 | dependency: transitive 504 | description: 505 | name: path 506 | url: "https://pub.dartlang.org" 507 | source: hosted 508 | version: "1.8.0" 509 | path_drawing: 510 | dependency: transitive 511 | description: 512 | name: path_drawing 513 | url: "https://pub.dartlang.org" 514 | source: hosted 515 | version: "1.0.0" 516 | path_parsing: 517 | dependency: transitive 518 | description: 519 | name: path_parsing 520 | url: "https://pub.dartlang.org" 521 | source: hosted 522 | version: "1.0.0" 523 | path_provider: 524 | dependency: transitive 525 | description: 526 | name: path_provider 527 | url: "https://pub.dartlang.org" 528 | source: hosted 529 | version: "2.0.8" 530 | path_provider_android: 531 | dependency: transitive 532 | description: 533 | name: path_provider_android 534 | url: "https://pub.dartlang.org" 535 | source: hosted 536 | version: "2.0.11" 537 | path_provider_ios: 538 | dependency: transitive 539 | description: 540 | name: path_provider_ios 541 | url: "https://pub.dartlang.org" 542 | source: hosted 543 | version: "2.0.7" 544 | path_provider_linux: 545 | dependency: transitive 546 | description: 547 | name: path_provider_linux 548 | url: "https://pub.dartlang.org" 549 | source: hosted 550 | version: "2.1.5" 551 | path_provider_macos: 552 | dependency: transitive 553 | description: 554 | name: path_provider_macos 555 | url: "https://pub.dartlang.org" 556 | source: hosted 557 | version: "2.0.5" 558 | path_provider_platform_interface: 559 | dependency: transitive 560 | description: 561 | name: path_provider_platform_interface 562 | url: "https://pub.dartlang.org" 563 | source: hosted 564 | version: "2.0.3" 565 | path_provider_windows: 566 | dependency: transitive 567 | description: 568 | name: path_provider_windows 569 | url: "https://pub.dartlang.org" 570 | source: hosted 571 | version: "2.0.5" 572 | pedantic: 573 | dependency: transitive 574 | description: 575 | name: pedantic 576 | url: "https://pub.dartlang.org" 577 | source: hosted 578 | version: "1.11.1" 579 | petitparser: 580 | dependency: transitive 581 | description: 582 | name: petitparser 583 | url: "https://pub.dartlang.org" 584 | source: hosted 585 | version: "4.4.0" 586 | platform: 587 | dependency: transitive 588 | description: 589 | name: platform 590 | url: "https://pub.dartlang.org" 591 | source: hosted 592 | version: "3.1.0" 593 | plugin_platform_interface: 594 | dependency: transitive 595 | description: 596 | name: plugin_platform_interface 597 | url: "https://pub.dartlang.org" 598 | source: hosted 599 | version: "2.1.2" 600 | pool: 601 | dependency: transitive 602 | description: 603 | name: pool 604 | url: "https://pub.dartlang.org" 605 | source: hosted 606 | version: "1.5.0" 607 | process: 608 | dependency: transitive 609 | description: 610 | name: process 611 | url: "https://pub.dartlang.org" 612 | source: hosted 613 | version: "4.2.4" 614 | provider: 615 | dependency: transitive 616 | description: 617 | name: provider 618 | url: "https://pub.dartlang.org" 619 | source: hosted 620 | version: "6.0.2" 621 | pub_semver: 622 | dependency: transitive 623 | description: 624 | name: pub_semver 625 | url: "https://pub.dartlang.org" 626 | source: hosted 627 | version: "2.1.0" 628 | pubspec_parse: 629 | dependency: transitive 630 | description: 631 | name: pubspec_parse 632 | url: "https://pub.dartlang.org" 633 | source: hosted 634 | version: "1.2.0" 635 | quiver: 636 | dependency: transitive 637 | description: 638 | name: quiver 639 | url: "https://pub.dartlang.org" 640 | source: hosted 641 | version: "3.0.1+1" 642 | responsive_builder: 643 | dependency: "direct main" 644 | description: 645 | name: responsive_builder 646 | url: "https://pub.dartlang.org" 647 | source: hosted 648 | version: "0.4.1" 649 | retrofit: 650 | dependency: "direct main" 651 | description: 652 | name: retrofit 653 | url: "https://pub.dartlang.org" 654 | source: hosted 655 | version: "3.0.1" 656 | retrofit_generator: 657 | dependency: "direct dev" 658 | description: 659 | name: retrofit_generator 660 | url: "https://pub.dartlang.org" 661 | source: hosted 662 | version: "3.0.1+1" 663 | rxdart: 664 | dependency: transitive 665 | description: 666 | name: rxdart 667 | url: "https://pub.dartlang.org" 668 | source: hosted 669 | version: "0.27.3" 670 | shared_preferences: 671 | dependency: "direct main" 672 | description: 673 | name: shared_preferences 674 | url: "https://pub.dartlang.org" 675 | source: hosted 676 | version: "2.0.12" 677 | shared_preferences_android: 678 | dependency: transitive 679 | description: 680 | name: shared_preferences_android 681 | url: "https://pub.dartlang.org" 682 | source: hosted 683 | version: "2.0.10" 684 | shared_preferences_ios: 685 | dependency: transitive 686 | description: 687 | name: shared_preferences_ios 688 | url: "https://pub.dartlang.org" 689 | source: hosted 690 | version: "2.0.9" 691 | shared_preferences_linux: 692 | dependency: transitive 693 | description: 694 | name: shared_preferences_linux 695 | url: "https://pub.dartlang.org" 696 | source: hosted 697 | version: "2.0.4" 698 | shared_preferences_macos: 699 | dependency: transitive 700 | description: 701 | name: shared_preferences_macos 702 | url: "https://pub.dartlang.org" 703 | source: hosted 704 | version: "2.0.2" 705 | shared_preferences_platform_interface: 706 | dependency: transitive 707 | description: 708 | name: shared_preferences_platform_interface 709 | url: "https://pub.dartlang.org" 710 | source: hosted 711 | version: "2.0.0" 712 | shared_preferences_web: 713 | dependency: transitive 714 | description: 715 | name: shared_preferences_web 716 | url: "https://pub.dartlang.org" 717 | source: hosted 718 | version: "2.0.3" 719 | shared_preferences_windows: 720 | dependency: transitive 721 | description: 722 | name: shared_preferences_windows 723 | url: "https://pub.dartlang.org" 724 | source: hosted 725 | version: "2.0.4" 726 | shelf: 727 | dependency: transitive 728 | description: 729 | name: shelf 730 | url: "https://pub.dartlang.org" 731 | source: hosted 732 | version: "1.2.0" 733 | shelf_packages_handler: 734 | dependency: transitive 735 | description: 736 | name: shelf_packages_handler 737 | url: "https://pub.dartlang.org" 738 | source: hosted 739 | version: "3.0.0" 740 | shelf_static: 741 | dependency: transitive 742 | description: 743 | name: shelf_static 744 | url: "https://pub.dartlang.org" 745 | source: hosted 746 | version: "1.1.0" 747 | shelf_web_socket: 748 | dependency: transitive 749 | description: 750 | name: shelf_web_socket 751 | url: "https://pub.dartlang.org" 752 | source: hosted 753 | version: "1.0.1" 754 | sky_engine: 755 | dependency: transitive 756 | description: flutter 757 | source: sdk 758 | version: "0.0.99" 759 | source_gen: 760 | dependency: transitive 761 | description: 762 | name: source_gen 763 | url: "https://pub.dartlang.org" 764 | source: hosted 765 | version: "1.2.1" 766 | source_helper: 767 | dependency: transitive 768 | description: 769 | name: source_helper 770 | url: "https://pub.dartlang.org" 771 | source: hosted 772 | version: "1.3.1" 773 | source_map_stack_trace: 774 | dependency: transitive 775 | description: 776 | name: source_map_stack_trace 777 | url: "https://pub.dartlang.org" 778 | source: hosted 779 | version: "2.1.0" 780 | source_maps: 781 | dependency: transitive 782 | description: 783 | name: source_maps 784 | url: "https://pub.dartlang.org" 785 | source: hosted 786 | version: "0.10.10" 787 | source_span: 788 | dependency: transitive 789 | description: 790 | name: source_span 791 | url: "https://pub.dartlang.org" 792 | source: hosted 793 | version: "1.8.1" 794 | sqflite: 795 | dependency: transitive 796 | description: 797 | name: sqflite 798 | url: "https://pub.dartlang.org" 799 | source: hosted 800 | version: "2.0.1" 801 | sqflite_common: 802 | dependency: transitive 803 | description: 804 | name: sqflite_common 805 | url: "https://pub.dartlang.org" 806 | source: hosted 807 | version: "2.2.0" 808 | stack_trace: 809 | dependency: transitive 810 | description: 811 | name: stack_trace 812 | url: "https://pub.dartlang.org" 813 | source: hosted 814 | version: "1.10.0" 815 | stream_channel: 816 | dependency: transitive 817 | description: 818 | name: stream_channel 819 | url: "https://pub.dartlang.org" 820 | source: hosted 821 | version: "2.1.0" 822 | stream_transform: 823 | dependency: transitive 824 | description: 825 | name: stream_transform 826 | url: "https://pub.dartlang.org" 827 | source: hosted 828 | version: "2.0.0" 829 | string_scanner: 830 | dependency: transitive 831 | description: 832 | name: string_scanner 833 | url: "https://pub.dartlang.org" 834 | source: hosted 835 | version: "1.1.0" 836 | synchronized: 837 | dependency: transitive 838 | description: 839 | name: synchronized 840 | url: "https://pub.dartlang.org" 841 | source: hosted 842 | version: "3.0.0" 843 | term_glyph: 844 | dependency: transitive 845 | description: 846 | name: term_glyph 847 | url: "https://pub.dartlang.org" 848 | source: hosted 849 | version: "1.2.0" 850 | test: 851 | dependency: transitive 852 | description: 853 | name: test 854 | url: "https://pub.dartlang.org" 855 | source: hosted 856 | version: "1.17.12" 857 | test_api: 858 | dependency: transitive 859 | description: 860 | name: test_api 861 | url: "https://pub.dartlang.org" 862 | source: hosted 863 | version: "0.4.3" 864 | test_core: 865 | dependency: transitive 866 | description: 867 | name: test_core 868 | url: "https://pub.dartlang.org" 869 | source: hosted 870 | version: "0.4.2" 871 | timing: 872 | dependency: transitive 873 | description: 874 | name: timing 875 | url: "https://pub.dartlang.org" 876 | source: hosted 877 | version: "1.0.0" 878 | tuple: 879 | dependency: transitive 880 | description: 881 | name: tuple 882 | url: "https://pub.dartlang.org" 883 | source: hosted 884 | version: "2.0.0" 885 | typed_data: 886 | dependency: transitive 887 | description: 888 | name: typed_data 889 | url: "https://pub.dartlang.org" 890 | source: hosted 891 | version: "1.3.0" 892 | universal_io: 893 | dependency: transitive 894 | description: 895 | name: universal_io 896 | url: "https://pub.dartlang.org" 897 | source: hosted 898 | version: "2.0.4" 899 | uuid: 900 | dependency: transitive 901 | description: 902 | name: uuid 903 | url: "https://pub.dartlang.org" 904 | source: hosted 905 | version: "3.0.5" 906 | vector_math: 907 | dependency: transitive 908 | description: 909 | name: vector_math 910 | url: "https://pub.dartlang.org" 911 | source: hosted 912 | version: "2.1.1" 913 | vm_service: 914 | dependency: transitive 915 | description: 916 | name: vm_service 917 | url: "https://pub.dartlang.org" 918 | source: hosted 919 | version: "7.5.0" 920 | watcher: 921 | dependency: transitive 922 | description: 923 | name: watcher 924 | url: "https://pub.dartlang.org" 925 | source: hosted 926 | version: "1.0.1" 927 | web_socket_channel: 928 | dependency: transitive 929 | description: 930 | name: web_socket_channel 931 | url: "https://pub.dartlang.org" 932 | source: hosted 933 | version: "2.1.0" 934 | webkit_inspection_protocol: 935 | dependency: transitive 936 | description: 937 | name: webkit_inspection_protocol 938 | url: "https://pub.dartlang.org" 939 | source: hosted 940 | version: "1.0.0" 941 | win32: 942 | dependency: transitive 943 | description: 944 | name: win32 945 | url: "https://pub.dartlang.org" 946 | source: hosted 947 | version: "2.3.3" 948 | xdg_directories: 949 | dependency: transitive 950 | description: 951 | name: xdg_directories 952 | url: "https://pub.dartlang.org" 953 | source: hosted 954 | version: "0.2.0" 955 | xml: 956 | dependency: transitive 957 | description: 958 | name: xml 959 | url: "https://pub.dartlang.org" 960 | source: hosted 961 | version: "5.3.1" 962 | yaml: 963 | dependency: transitive 964 | description: 965 | name: yaml 966 | url: "https://pub.dartlang.org" 967 | source: hosted 968 | version: "3.1.0" 969 | sdks: 970 | dart: ">=2.15.1 <3.0.0" 971 | flutter: ">=2.5.0" 972 | --------------------------------------------------------------------------------