├── ios ├── 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 │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.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 ├── Podfile.lock └── Podfile ├── assets ├── config │ └── config.json.temp └── l10n │ ├── strings_en.arb │ └── strings_es.arb ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_movies_app │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── analysis_options.yaml ├── lib ├── domain │ ├── exception │ │ ├── mapper_exception.dart │ │ └── network_exception.dart │ ├── model │ │ ├── config.dart │ │ ├── movie.dart │ │ ├── config.freezed.dart │ │ └── movie.freezed.dart │ └── service │ │ └── app_service.dart ├── data │ ├── datasource │ │ └── preferences.dart │ ├── repository │ │ ├── preferences_repository.dart │ │ └── movies_repository.dart │ ├── database │ │ ├── entity │ │ │ ├── movie_db_entity.g.dart │ │ │ └── movie_db_entity.dart │ │ ├── dao │ │ │ ├── movies_dao.dart │ │ │ └── base_dao.dart │ │ └── database_mapper.dart │ └── network │ │ ├── network_mapper.dart │ │ ├── client │ │ └── api_client.dart │ │ └── entity │ │ ├── movie_entity.dart │ │ └── movie_entity.g.dart ├── util │ └── l10n │ │ ├── app_localizations_en.dart │ │ ├── app_localizations_es.dart │ │ └── app_localizations.dart ├── presentation │ ├── list │ │ ├── movies_list_model.dart │ │ ├── movie_preview.dart │ │ └── movies_list_screen.dart │ ├── app │ │ └── app.dart │ └── detail │ │ └── movie_detail_screen.dart └── main.dart ├── test ├── mocks.dart ├── data │ └── repository │ │ ├── preferences_repository_test.dart │ │ └── movies_repository_test.dart └── presentation.list │ └── movie_preview_test.dart ├── pubspec.yaml ├── .metadata ├── README.md ├── .gitignore ├── LICENSE └── pubspec.lock /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/config/config.json.temp: -------------------------------------------------------------------------------- 1 | { 2 | "apiKey": "your_api_key", 3 | "apiHost": "your_api_host" 4 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svprdga/Flutter-Movies-App/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: [ lib/data/network/entity/*.g.dart , lib/util/l10n/** ] 5 | 6 | linter: 7 | rules: 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svprdga/Flutter-Movies-App/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/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/svprdga/Flutter-Movies-App/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_movies_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_movies_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /assets/l10n/strings_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Movies App", 3 | "toggleLightDart": "Toggle light/dark mode", 4 | "upcomingMovies": "Upcoming movies", 5 | "refresh": "Refresh", 6 | "getNewData": "Refresh to fetch new data" 7 | } -------------------------------------------------------------------------------- /assets/l10n/strings_es.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "App de Películas", 3 | "toggleLightDart": "Intercambiar modo claro/oscuro", 4 | "upcomingMovies": "Próximas películas", 5 | "refresh": "Refrescar", 6 | "getNewData": "Refrescar para obtener datos nuevos" 7 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /lib/domain/exception/mapper_exception.dart: -------------------------------------------------------------------------------- 1 | class MapperException implements Exception { 2 | final String message; 3 | 4 | MapperException(this.message); 5 | 6 | @override 7 | String toString() => 'Error when mapping class $From to $To: $message'; 8 | } 9 | -------------------------------------------------------------------------------- /lib/domain/model/config.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'config.freezed.dart'; 4 | 5 | @freezed 6 | class Config with _$Config { 7 | const factory Config({ 8 | required String apiKey, 9 | required String apiHost, 10 | }) = _Config; 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/domain/exception/network_exception.dart: -------------------------------------------------------------------------------- 1 | class NetworkException implements Exception { 2 | final int statusCode; 3 | String? message; 4 | 5 | NetworkException({required this.statusCode, this.message}); 6 | 7 | @override 8 | String toString() => 9 | 'NetworkException: status code $statusCode, message: $message'; 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/model/movie.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'movie.freezed.dart'; 4 | 5 | @freezed 6 | class Movie with _$Movie { 7 | const factory Movie({ 8 | required String id, 9 | required String title, 10 | String? imageUrl, 11 | required DateTime releaseDate, 12 | }) = _Movie; 13 | } 14 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/data/datasource/preferences.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | class Preferences { 5 | static const keyThemeMode = 'theme_mode'; 6 | final defThemeMode = ThemeMode.light.index; 7 | 8 | final SharedPreferences prefs; 9 | 10 | Preferences({required this.prefs}); 11 | 12 | Future getThemeMode() async { 13 | return prefs.getInt(keyThemeMode) ?? defThemeMode; 14 | } 15 | 16 | Future setThemeMode(int value) async { 17 | await prefs.setInt(keyThemeMode, value); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/util/l10n/app_localizations_en.dart: -------------------------------------------------------------------------------- 1 | import 'app_localizations.dart'; 2 | 3 | /// The translations for English (`en`). 4 | class AppLocalizationsEn extends AppLocalizations { 5 | AppLocalizationsEn([String locale = 'en']) : super(locale); 6 | 7 | @override 8 | String get appName => 'Movies App'; 9 | 10 | @override 11 | String get toggleLightDart => 'Toggle light/dark mode'; 12 | 13 | @override 14 | String get upcomingMovies => 'Upcoming movies'; 15 | 16 | @override 17 | String get refresh => 'Refresh'; 18 | 19 | @override 20 | String get getNewData => 'Refresh to fetch new data'; 21 | } 22 | -------------------------------------------------------------------------------- /lib/util/l10n/app_localizations_es.dart: -------------------------------------------------------------------------------- 1 | import 'app_localizations.dart'; 2 | 3 | /// The translations for Spanish Castilian (`es`). 4 | class AppLocalizationsEs extends AppLocalizations { 5 | AppLocalizationsEs([String locale = 'es']) : super(locale); 6 | 7 | @override 8 | String get appName => 'App de Películas'; 9 | 10 | @override 11 | String get toggleLightDart => 'Intercambiar modo claro/oscuro'; 12 | 13 | @override 14 | String get upcomingMovies => 'Próximas películas'; 15 | 16 | @override 17 | String get refresh => 'Refrescar'; 18 | 19 | @override 20 | String get getNewData => 'Refrescar para obtener datos nuevos'; 21 | } 22 | -------------------------------------------------------------------------------- /lib/data/repository/preferences_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/data/datasource/preferences.dart'; 3 | 4 | class PreferencesRepository { 5 | final Preferences preferences; 6 | 7 | PreferencesRepository({required this.preferences}); 8 | 9 | Future setThemeMode(ThemeMode mode) async { 10 | await preferences.setThemeMode(mode.index); 11 | } 12 | 13 | Future getThemeMode() async { 14 | final rawValue = await preferences.getThemeMode(); 15 | 16 | if (rawValue == ThemeMode.light.index) { 17 | return ThemeMode.light; 18 | } else { 19 | return ThemeMode.dark; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/domain/service/app_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/data/repository/preferences_repository.dart'; 3 | 4 | class AppService extends ChangeNotifier { 5 | final PreferencesRepository preferencesRepo; 6 | 7 | late ThemeMode _themeMode; 8 | 9 | ThemeMode get themeMode => _themeMode; 10 | 11 | set themeMode(ThemeMode value) { 12 | if (_themeMode != value) { 13 | _themeMode = value; 14 | notifyListeners(); 15 | preferencesRepo.setThemeMode(value); 16 | } 17 | } 18 | 19 | AppService({required this.preferencesRepo}); 20 | 21 | Future load() async { 22 | _themeMode = await preferencesRepo.getThemeMode(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.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 | -------------------------------------------------------------------------------- /test/mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/database/dao/movies_dao.dart'; 2 | import 'package:flutter_movies_app/data/database/database_mapper.dart'; 3 | import 'package:flutter_movies_app/data/datasource/preferences.dart'; 4 | import 'package:flutter_movies_app/data/network/client/api_client.dart'; 5 | import 'package:flutter_movies_app/data/network/network_mapper.dart'; 6 | import 'package:mocktail/mocktail.dart'; 7 | 8 | class ApiClientMock extends Mock implements ApiClient {} 9 | 10 | class NetworkMapperMock extends Mock implements NetworkMapper {} 11 | 12 | class MoviesDaoMock extends Mock implements MoviesDao {} 13 | 14 | class DatabaseMapperMock extends Mock implements DatabaseMapper {} 15 | 16 | class PreferencesMock extends Mock implements Preferences {} 17 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_movies_app 2 | description: A sample Flutter app that shows a list of upcoming movies. 3 | publish_to: 'none' 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=3.0.1 <4.0.0' 9 | 10 | dependencies: 11 | cached_network_image: ^3.2.3 12 | dio: ^5.1.1 13 | flutter: 14 | sdk: flutter 15 | flutter_localizations: 16 | sdk: flutter 17 | freezed_annotation: ^2.2.0 18 | infinite_scroll_pagination: ^3.2.0 19 | intl: ^0.19.0 20 | json_annotation: ^4.8.0 21 | logger: ^2.0.1 22 | path: ^1.8.2 23 | path_provider: ^2.0.14 24 | provider: ^6.0.5 25 | shared_preferences: ^2.1.0 26 | sqflite: ^2.2.6 27 | 28 | dev_dependencies: 29 | build_runner: ^2.3.3 30 | flutter_test: 31 | sdk: flutter 32 | freezed: ^2.3.2 33 | json_serializable: ^6.6.1 34 | lint: ^2.0.1 35 | mocktail: ^0.3.0 36 | 37 | flutter: 38 | uses-material-design: true 39 | assets: 40 | - assets/config/ 41 | -------------------------------------------------------------------------------- /lib/data/database/entity/movie_db_entity.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'movie_db_entity.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | MovieDbEntity _$MovieDbEntityFromJson(Map json) => 10 | MovieDbEntity( 11 | id: json['movie_id'] as int?, 12 | movieId: json['movie_movie_id'] as String, 13 | title: json['movie_title'] as String, 14 | imageUrl: json['movie_image_url'] as String?, 15 | releaseDate: json['movie_release_date'] as int, 16 | ); 17 | 18 | Map _$MovieDbEntityToJson(MovieDbEntity instance) => 19 | { 20 | 'movie_id': instance.id, 21 | 'movie_movie_id': instance.movieId, 22 | 'movie_title': instance.title, 23 | 'movie_image_url': instance.imageUrl, 24 | 'movie_release_date': instance.releaseDate, 25 | }; 26 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/data/database/entity/movie_db_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'movie_db_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class MovieDbEntity { 7 | static const fieldId = 'movie_id'; 8 | static const fieldMovieId = 'movie_movie_id'; 9 | static const fieldTitle = 'movie_title'; 10 | static const fieldImageUrl = 'movie_image_url'; 11 | static const fieldReleaseDate = 'movie_release_date'; 12 | 13 | @JsonKey(name: fieldId) 14 | final int? id; 15 | @JsonKey(name: fieldMovieId) 16 | final String movieId; 17 | @JsonKey(name: fieldTitle) 18 | final String title; 19 | @JsonKey(name: fieldImageUrl) 20 | final String? imageUrl; 21 | @JsonKey(name: fieldReleaseDate) 22 | final int releaseDate; 23 | 24 | MovieDbEntity({ 25 | required this.id, 26 | required this.movieId, 27 | required this.title, 28 | this.imageUrl, 29 | required this.releaseDate, 30 | }); 31 | 32 | factory MovieDbEntity.fromJson(Map json) => 33 | _$MovieDbEntityFromJson(json); 34 | 35 | Map toJson() => _$MovieDbEntityToJson(this); 36 | } 37 | -------------------------------------------------------------------------------- /lib/presentation/list/movies_list_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/repository/movies_repository.dart'; 2 | import 'package:flutter_movies_app/domain/model/movie.dart'; 3 | import 'package:logger/logger.dart'; 4 | 5 | class MoviesListModel { 6 | final Logger log; 7 | final MoviesRepository moviesRepo; 8 | 9 | MoviesListModel({required this.log, required this.moviesRepo}); 10 | 11 | Future> fetchPage(int page) async { 12 | try { 13 | return await moviesRepo.getUpcomingMovies(limit: 10, page: page); 14 | } catch (e) { 15 | log.e('Error when fetching page $page', error: e); 16 | rethrow; 17 | } 18 | } 19 | 20 | Future deletePersistedMovies() async { 21 | try { 22 | await moviesRepo.deleteAll(); 23 | } catch (e) { 24 | log.e('Error when deleting movies', error: e); 25 | rethrow; 26 | } 27 | } 28 | 29 | Future hasNewData() async { 30 | try { 31 | return await moviesRepo.checkNewData(); 32 | } catch (e) { 33 | log.e('Error when checking for new data', error: e); 34 | return true; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 17 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 18 | - platform: android 19 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 20 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 21 | - platform: ios 22 | create_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 23 | base_revision: 90c64ed42ba53a52d18f0cb3b17666c8662ed2a0 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /lib/data/network/network_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/network/entity/movie_entity.dart'; 2 | import 'package:flutter_movies_app/domain/exception/mapper_exception.dart'; 3 | import 'package:flutter_movies_app/domain/model/movie.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | class NetworkMapper { 7 | final Logger log; 8 | 9 | NetworkMapper({required this.log}); 10 | 11 | Movie toMovie(MovieEntity entity) { 12 | try { 13 | return Movie( 14 | id: entity.id, 15 | title: entity.titleText.text, 16 | imageUrl: entity.primaryImage?.url, 17 | releaseDate: DateTime( 18 | entity.releaseDate.year, 19 | entity.releaseDate.month, 20 | entity.releaseDate.day, 21 | ), 22 | ); 23 | } catch (e) { 24 | throw MapperException(e.toString()); 25 | } 26 | } 27 | 28 | List toMovies(List entities) { 29 | final List movies = []; 30 | 31 | for (final entity in entities) { 32 | try { 33 | movies.add(toMovie(entity)); 34 | } catch (e) { 35 | log.w('Could not map entity ${entity.id}', error: e); 36 | } 37 | } 38 | 39 | return movies; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/data/database/dao/movies_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/database/dao/base_dao.dart'; 2 | import 'package:flutter_movies_app/data/database/entity/movie_db_entity.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | class MoviesDao extends BaseDao { 6 | Future> selectAll({ 7 | int? limit, 8 | int? offset, 9 | }) async { 10 | final Database db = await getDb(); 11 | final List> maps = await db.query( 12 | BaseDao.moviesTableName, 13 | limit: limit, 14 | offset: offset, 15 | orderBy: '${MovieDbEntity.fieldId} ASC', 16 | ); 17 | return List.generate(maps.length, (i) { 18 | return MovieDbEntity.fromJson(maps[i]); 19 | }); 20 | } 21 | 22 | Future insert(MovieDbEntity entity) async { 23 | final Database db = await getDb(); 24 | await db.insert(BaseDao.moviesTableName, entity.toJson()); 25 | } 26 | 27 | Future insertAll(List entities) async { 28 | final Database db = await getDb(); 29 | await db.transaction((transaction) async { 30 | for (final entity in entities) { 31 | transaction.insert(BaseDao.moviesTableName, entity.toJson()); 32 | } 33 | }); 34 | } 35 | 36 | Future deleteAll() async { 37 | final Database db = await getDb(); 38 | await db.delete(BaseDao.moviesTableName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/data/database/dao/base_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/data/database/entity/movie_db_entity.dart'; 3 | import 'package:path/path.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | 6 | abstract class BaseDao { 7 | static const databaseVersion = 1; 8 | 9 | static const _databaseName = 'com.my.app.db'; 10 | 11 | static const moviesTableName = 'movies'; 12 | 13 | Database? _database; 14 | 15 | @protected 16 | Future getDb() async { 17 | _database ??= await _getDatabase(); 18 | return _database!; 19 | } 20 | 21 | Future _getDatabase() async { 22 | return openDatabase( 23 | join(await getDatabasesPath(), _databaseName), 24 | onCreate: (db, version) async { 25 | final batch = db.batch(); 26 | _createMoviesTableV1(batch); 27 | await batch.commit(); 28 | }, 29 | version: databaseVersion, 30 | ); 31 | } 32 | 33 | void _createMoviesTableV1(Batch batch) { 34 | batch.execute( 35 | ''' 36 | CREATE TABLE $moviesTableName( 37 | ${MovieDbEntity.fieldId} INTEGER PRIMARY KEY AUTOINCREMENT, 38 | ${MovieDbEntity.fieldMovieId} TEXT NOT NULL, 39 | ${MovieDbEntity.fieldTitle} TEXT NOT NULL, 40 | ${MovieDbEntity.fieldImageUrl} TEXT NULL, 41 | ${MovieDbEntity.fieldReleaseDate} INTEGER NOT NULL 42 | ); 43 | ''', 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/data/network/client/api_client.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_movies_app/data/network/entity/movie_entity.dart'; 3 | import 'package:flutter_movies_app/domain/exception/network_exception.dart'; 4 | 5 | class ApiClient { 6 | late final Dio _dio; 7 | 8 | ApiClient({ 9 | required String baseUrl, 10 | required String apiKey, 11 | required String apiHost, 12 | }) { 13 | _dio = Dio() 14 | ..options.baseUrl = baseUrl 15 | ..options.headers = { 16 | 'X-RapidAPI-Key': apiKey, 17 | 'X-RapidAPI-Host': apiHost, 18 | } 19 | ..interceptors.add( 20 | LogInterceptor( 21 | requestBody: true, 22 | responseBody: true, 23 | ), 24 | ); 25 | } 26 | 27 | Future getUpcomingMovies({ 28 | required int page, 29 | int? limit, 30 | }) async { 31 | final response = await _dio.get( 32 | 'titles/x/upcoming', 33 | queryParameters: { 34 | 'page': page, 35 | 'limit': limit, 36 | }, 37 | ); 38 | 39 | if (response.statusCode != null && response.statusCode! >= 400) { 40 | throw NetworkException( 41 | statusCode: response.statusCode!, 42 | message: response.statusMessage, 43 | ); 44 | } else if (response.statusCode != null) { 45 | return UpcomingMovies.fromJson(response.data as Map); 46 | } else { 47 | throw Exception('Unknown error'); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - path_provider_foundation (0.0.1): 7 | - Flutter 8 | - FlutterMacOS 9 | - shared_preferences_foundation (0.0.1): 10 | - Flutter 11 | - FlutterMacOS 12 | - sqflite (0.0.3): 13 | - Flutter 14 | - FMDB (>= 2.7.5) 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 19 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 20 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 21 | 22 | SPEC REPOS: 23 | trunk: 24 | - FMDB 25 | 26 | EXTERNAL SOURCES: 27 | Flutter: 28 | :path: Flutter 29 | path_provider_foundation: 30 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 31 | shared_preferences_foundation: 32 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 33 | sqflite: 34 | :path: ".symlinks/plugins/sqflite/ios" 35 | 36 | SPEC CHECKSUMS: 37 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 38 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 39 | path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 40 | shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 41 | sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a 42 | 43 | PODFILE CHECKSUM: a18f961bdff21dc76b05e65fe812b1788db6f577 44 | 45 | COCOAPODS: 1.12.1 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | 42 | installer.generated_projects.each do |project| 43 | project.targets.each do |target| 44 | target.build_configurations.each do |config| 45 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/presentation/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | import 'package:flutter_movies_app/domain/service/app_service.dart'; 4 | import 'package:flutter_movies_app/main.dart'; 5 | import 'package:flutter_movies_app/presentation/list/movies_list_screen.dart'; 6 | import 'package:flutter_movies_app/util/l10n/app_localizations.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class App extends StatelessWidget { 10 | final InitialData data; 11 | 12 | const App({required this.data}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return MultiProvider( 17 | providers: data.providers, 18 | child: Consumer( 19 | builder: (context, service, child) => MaterialApp( 20 | title: 'Movies App', 21 | theme: ThemeData( 22 | primarySwatch: Colors.green, 23 | brightness: Brightness.light, 24 | ), 25 | darkTheme: ThemeData( 26 | primarySwatch: Colors.green, 27 | brightness: Brightness.dark, 28 | textButtonTheme: TextButtonThemeData( 29 | style: ButtonStyle( 30 | foregroundColor: MaterialStateProperty.all(Colors.blue), 31 | ), 32 | ), 33 | ), 34 | themeMode: service.themeMode, 35 | supportedLocales: const [ 36 | Locale('en', ''), 37 | Locale('es', ''), 38 | ], 39 | localizationsDelegates: const [ 40 | AppLocalizations.delegate, 41 | GlobalMaterialLocalizations.delegate, 42 | GlobalWidgetsLocalizations.delegate, 43 | GlobalCupertinoLocalizations.delegate, 44 | ], 45 | home: MoviesListScreen(), 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Flutter Movies App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_movies_app 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/data/database/database_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/database/entity/movie_db_entity.dart'; 2 | import 'package:flutter_movies_app/domain/exception/mapper_exception.dart'; 3 | import 'package:flutter_movies_app/domain/model/movie.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | class DatabaseMapper { 7 | final Logger log; 8 | 9 | DatabaseMapper({required this.log}); 10 | 11 | Movie toMovie(MovieDbEntity entity) { 12 | try { 13 | return Movie( 14 | id: entity.movieId, 15 | title: entity.title, 16 | imageUrl: entity.imageUrl, 17 | releaseDate: DateTime.fromMillisecondsSinceEpoch(entity.releaseDate), 18 | ); 19 | } catch (e) { 20 | throw MapperException(e.toString()); 21 | } 22 | } 23 | 24 | List toMovies(List entities) { 25 | final List movies = []; 26 | 27 | for (final entity in entities) { 28 | try { 29 | movies.add(toMovie(entity)); 30 | } catch (e) { 31 | log.w('Could not map entity ${entity.movieId}', error: e); 32 | } 33 | } 34 | 35 | return movies; 36 | } 37 | 38 | MovieDbEntity toMovieDbEntity(Movie movie) { 39 | try { 40 | return MovieDbEntity( 41 | id: null, 42 | movieId: movie.id, 43 | title: movie.title, 44 | imageUrl: movie.imageUrl, 45 | releaseDate: movie.releaseDate.millisecondsSinceEpoch, 46 | ); 47 | } catch (e) { 48 | throw MapperException(e.toString()); 49 | } 50 | } 51 | 52 | List toMovieDbEntities(List movies) { 53 | final List entities = []; 54 | 55 | for (final movie in movies) { 56 | try { 57 | entities.add(toMovieDbEntity(movie)); 58 | } catch (e) { 59 | log.w('Could not map movie ${movie.id}', error: e); 60 | } 61 | } 62 | 63 | return entities; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/data/repository/movies_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/database/dao/movies_dao.dart'; 2 | import 'package:flutter_movies_app/data/database/database_mapper.dart'; 3 | import 'package:flutter_movies_app/data/network/client/api_client.dart'; 4 | import 'package:flutter_movies_app/data/network/network_mapper.dart'; 5 | import 'package:flutter_movies_app/domain/model/movie.dart'; 6 | 7 | class MoviesRepository { 8 | final ApiClient apiClient; 9 | final NetworkMapper networkMapper; 10 | final MoviesDao moviesDao; 11 | final DatabaseMapper databaseMapper; 12 | 13 | MoviesRepository({ 14 | required this.apiClient, 15 | required this.networkMapper, 16 | required this.moviesDao, 17 | required this.databaseMapper, 18 | }); 19 | 20 | Future> getUpcomingMovies({ 21 | required int limit, 22 | required int page, 23 | }) async { 24 | // Try to load the movies from the database 25 | final dbEntities = 26 | await moviesDao.selectAll(limit: limit, offset: (page * limit) - limit); 27 | 28 | if (dbEntities.isNotEmpty) { 29 | return databaseMapper.toMovies(dbEntities); 30 | } 31 | 32 | // Fetch movies from remote API 33 | final entities = await apiClient.getUpcomingMovies(page: page); 34 | final movies = networkMapper.toMovies(entities.results); 35 | 36 | // Save movies to database 37 | moviesDao.insertAll(databaseMapper.toMovieDbEntities(movies)); 38 | 39 | return movies; 40 | } 41 | 42 | Future deleteAll() async => moviesDao.deleteAll(); 43 | 44 | Future checkNewData() async { 45 | final entities = await moviesDao.selectAll(limit: 1); 46 | 47 | if (entities.isEmpty) { 48 | return false; 49 | } 50 | 51 | final entity = entities.first; 52 | 53 | final movies = await apiClient.getUpcomingMovies(page: 1, limit: 1); 54 | 55 | if (entity.movieId == movies.results.first.id) { 56 | return false; 57 | } else { 58 | return true; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.flutter_movies_app" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Movies App 2 | 3 | This is a sample Flutter project demonstrating different best practices on how to build a hybrid app for Android and iOS. 4 | 5 | ## How to build the project 6 | 7 | This app uses an API to get a list of the latest movies, you can see the details of said API in the following [link](https://rapidapi.com/SAdrian/api/moviesdatabase/). 8 | 9 | Follow these steps to be able to build the project: 10 | 11 | 1. Sign up at the link provided 12 | 2. Copy the file located in `/assets/config/config.json.temp` and rename it to `config.json` 13 | 3. Replace the `apiKey` and `apiHost` values with those provided when registering to this API 14 | 15 | Once this is done, the app should be able to run on both Android and iOS (including emulators and simulators). 16 | 17 | ## What is included in this example 18 | 19 | - Clean architecture: this example tries to reproduce the basic SOLID principles, without being completely strict with the basic principles of clean architecture; it's about getting a good trade-off between robustness, agility, scalability, and maintainability. 20 | - Database persistence: it can be seen that the movie data obtained from the API is persisted in a local database in order to offer an interface with content to the user once the application is opened, in addition to saving bandwidth. A system is also included to be able to notify the user in the event that new data is detected, provided that such data can be obtained. 21 | - The [provider](https://pub.dev/packages/provider) library is used as state management. 22 | - The app can toggle between light and dark theme, in addition, this choice persists between sessions. 23 | - For model management, we have chosen to use [freezed](https://pub.dev/packages/freezed), as it provides an assortment of methods and utilities that perfectly complement the Dart classes. In addition, [json_serializable](https://pub.dev/packages/json_serializable) is used for the data layer models to speed up their creation and maintenance. 24 | - Unit test: a small sample on how to implement a unit test has been added. This file can be found in the `test` directory and can be run with `flutter test`. 25 | 26 | ## Additional Information 27 | 28 | The domain and data layer models can be generated with the following command: 29 | 30 | `flutter pub run build_runner build --delete-conflicting-outputs` 31 | 32 | Translation files can also be generated with: 33 | 34 | `flutter gen-l10n --arb-dir=assets/l10n --template-arb-file=strings_en.arb --output-localization-file=app_localizations.dart --output-class=AppLocalizations --output-dir=lib/util/l10n --no-synthetic-package` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | comments_sheet 12 | migrate_working_dir/ 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter repo-specific 26 | /bin/cache/ 27 | /bin/mingit/ 28 | /dev/benchmarks/mega_gallery/ 29 | /dev/bots/.recipe_deps 30 | /dev/bots/android_tools/ 31 | /dev/docs/doc/ 32 | /dev/docs/flutter.docs.zip 33 | /dev/docs/lib/ 34 | /dev/docs/pubspec.yaml 35 | /packages/flutter/coverage/ 36 | version 37 | 38 | # Flutter/Dart/Pub related 39 | **/doc/api/ 40 | **/ios/Flutter/.last_build_id 41 | .dart_tool/ 42 | .flutter-plugins 43 | .flutter-plugins-dependencies 44 | .packages 45 | .pub-cache/ 46 | .pub/ 47 | /build/ 48 | flutter_*.png 49 | linked_*.ds 50 | unlinked.ds 51 | unlinked_spec.ds 52 | 53 | # Android related 54 | **/android/**/gradle-wrapper.jar 55 | **/android/.gradle 56 | **/android/captures/ 57 | **/android/gradlew 58 | **/android/gradlew.bat 59 | **/android/local.properties 60 | **/android/**/GeneratedPluginRegistrant.java 61 | **/android/projectFilesBackup/ 62 | **/android/key.properties 63 | **/android/nativetemplates/build/ 64 | /android/app/debug 65 | /android/app/profile 66 | /android/app/release 67 | 68 | # iOS/XCode related 69 | **/ios/**/*.mode1v3 70 | **/ios/**/*.mode2v3 71 | **/ios/**/*.moved-aside 72 | **/ios/**/*.pbxuser 73 | **/ios/**/*.perspectivev3 74 | **/ios/**/*sync/ 75 | **/ios/**/.sconsign.dblite 76 | **/ios/**/.tags* 77 | **/ios/**/.vagrant/ 78 | **/ios/**/DerivedData/ 79 | **/ios/**/Icon? 80 | **/ios/**/Pods/ 81 | **/ios/**/.symlinks/ 82 | **/ios/**/profile 83 | **/ios/**/xcuserdata 84 | **/ios/.generated/ 85 | **/ios/Flutter/App.framework 86 | **/ios/Flutter/Flutter.framework 87 | **/ios/Flutter/Generated.xcconfig 88 | **/ios/Flutter/app.flx 89 | **/ios/Flutter/app.zip 90 | **/ios/Flutter/flutter_assets/ 91 | **/ios/Flutter/flutter_export_environment.sh 92 | **/ios/ServiceDefinitions.json 93 | **/ios/Runner/GeneratedPluginRegistrant.* 94 | **/ios/build 95 | 96 | # Exceptions to above rules. 97 | !**/ios/**/default.mode1v3 98 | !**/ios/**/default.mode2v3 99 | !**/ios/**/default.pbxuser 100 | !**/ios/**/default.perspectivev3 101 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 102 | 103 | # Web related 104 | lib/generated_plugin_registrant.dart 105 | 106 | # Symbolication related 107 | app.*.symbols 108 | 109 | # Obfuscation related 110 | app.*.map.json 111 | 112 | # Test coverage 113 | coverage 114 | 115 | # Config file 116 | assets/config/config.json 117 | 118 | # Flutter shader files 119 | *.sksl.json 120 | 121 | -------------------------------------------------------------------------------- /test/data/repository/preferences_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/data/datasource/preferences.dart'; 3 | import 'package:flutter_movies_app/data/repository/preferences_repository.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:mocktail/mocktail.dart'; 6 | 7 | import '../../mocks.dart'; 8 | 9 | void main() { 10 | group('PreferencesRepository -', () { 11 | late PreferencesRepository preferencesRepo; 12 | 13 | late Preferences preferences; 14 | 15 | setUp(() { 16 | preferences = PreferencesMock(); 17 | 18 | when(() => preferences.setThemeMode(any())) 19 | .thenAnswer((invocation) => Future.value()); 20 | 21 | preferencesRepo = PreferencesRepository(preferences: preferences); 22 | }); 23 | 24 | group('when calling setThemeMode()', () { 25 | group('given ThemeMode is system', () { 26 | test("should save the 'system' value of ThemeMode", () async { 27 | await preferencesRepo.setThemeMode(ThemeMode.system); 28 | final captured = 29 | verify(() => preferences.setThemeMode(captureAny())).captured; 30 | expect(captured[0], ThemeMode.system.index); 31 | }); 32 | }); 33 | 34 | group('given ThemeMode is light', () { 35 | test("should save the 'light' value of ThemeMode", () async { 36 | await preferencesRepo.setThemeMode(ThemeMode.light); 37 | final captured = 38 | verify(() => preferences.setThemeMode(captureAny())).captured; 39 | expect(captured[0], ThemeMode.light.index); 40 | }); 41 | }); 42 | 43 | group('given ThemeMode is dark', () { 44 | test("should save the 'system' value of ThemeMode", () async { 45 | await preferencesRepo.setThemeMode(ThemeMode.dark); 46 | final captured = 47 | verify(() => preferences.setThemeMode(captureAny())).captured; 48 | expect(captured[0], ThemeMode.dark.index); 49 | }); 50 | }); 51 | }); 52 | 53 | group('when calling getThemeMode()', () { 54 | group('given saved parameter is light', () { 55 | test('should return ThemeMode.light value', () async { 56 | when(() => preferences.getThemeMode()) 57 | .thenAnswer((_) => Future.value(ThemeMode.light.index)); 58 | final result = await preferencesRepo.getThemeMode(); 59 | expect(result, ThemeMode.light); 60 | }); 61 | }); 62 | 63 | group('given saved parameter is dark', () { 64 | test('should return ThemeMode.dark value', () async { 65 | when(() => preferences.getThemeMode()) 66 | .thenAnswer((_) => Future.value(ThemeMode.dark.index)); 67 | final result = await preferencesRepo.getThemeMode(); 68 | expect(result, ThemeMode.dark); 69 | }); 70 | }); 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /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/network/entity/movie_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'movie_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class UpcomingMovies { 7 | UpcomingMovies({ 8 | required this.next, 9 | required this.entries, 10 | required this.results, 11 | }); 12 | 13 | String next; 14 | int entries; 15 | List results; 16 | 17 | factory UpcomingMovies.fromJson(Map json) => 18 | _$UpcomingMoviesFromJson(json); 19 | } 20 | 21 | @JsonSerializable() 22 | class MovieEntity { 23 | MovieEntity({ 24 | required this.id, 25 | this.primaryImage, 26 | required this.titleType, 27 | required this.titleText, 28 | this.releaseYear, 29 | required this.releaseDate, 30 | }); 31 | 32 | String id; 33 | PrimaryImage? primaryImage; 34 | TitleType titleType; 35 | TitleText titleText; 36 | ReleaseYear? releaseYear; 37 | ReleaseDate releaseDate; 38 | 39 | factory MovieEntity.fromJson(Map json) => 40 | _$MovieEntityFromJson(json); 41 | } 42 | 43 | @JsonSerializable() 44 | class PrimaryImage { 45 | PrimaryImage({ 46 | required this.id, 47 | required this.width, 48 | required this.height, 49 | required this.url, 50 | required this.caption, 51 | }); 52 | 53 | String id; 54 | int width; 55 | int height; 56 | String url; 57 | Caption caption; 58 | 59 | factory PrimaryImage.fromJson(Map json) => 60 | _$PrimaryImageFromJson(json); 61 | } 62 | 63 | @JsonSerializable() 64 | class Caption { 65 | Caption({ 66 | required this.plainText, 67 | }); 68 | 69 | String plainText; 70 | 71 | factory Caption.fromJson(Map json) => 72 | _$CaptionFromJson(json); 73 | } 74 | 75 | @JsonSerializable() 76 | class ReleaseDate { 77 | ReleaseDate({ 78 | required this.day, 79 | required this.month, 80 | required this.year, 81 | }); 82 | 83 | int day; 84 | int month; 85 | int year; 86 | 87 | factory ReleaseDate.fromJson(Map json) => 88 | _$ReleaseDateFromJson(json); 89 | } 90 | 91 | @JsonSerializable() 92 | class ReleaseYear { 93 | ReleaseYear({ 94 | required this.year, 95 | this.endYear, 96 | }); 97 | 98 | int year; 99 | dynamic endYear; 100 | 101 | factory ReleaseYear.fromJson(Map json) => 102 | _$ReleaseYearFromJson(json); 103 | } 104 | 105 | @JsonSerializable() 106 | class TitleText { 107 | TitleText({ 108 | required this.text, 109 | }); 110 | 111 | String text; 112 | 113 | factory TitleText.fromJson(Map json) => 114 | _$TitleTextFromJson(json); 115 | } 116 | 117 | @JsonSerializable() 118 | class TitleType { 119 | TitleType({ 120 | required this.text, 121 | required this.id, 122 | required this.isSeries, 123 | required this.isEpisode, 124 | this.categories, 125 | this.canHaveEpisodes, 126 | }); 127 | 128 | String text; 129 | String id; 130 | bool isSeries; 131 | bool isEpisode; 132 | List? categories; 133 | bool? canHaveEpisodes; 134 | 135 | factory TitleType.fromJson(Map json) => 136 | _$TitleTypeFromJson(json); 137 | } 138 | 139 | @JsonSerializable() 140 | class Category { 141 | Category({ 142 | required this.value, 143 | }); 144 | 145 | String value; 146 | 147 | factory Category.fromJson(Map json) => 148 | _$CategoryFromJson(json); 149 | } 150 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/presentation.list/movie_preview_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/domain/model/movie.dart'; 3 | import 'package:flutter_movies_app/presentation/detail/movie_detail_screen.dart'; 4 | import 'package:flutter_movies_app/presentation/list/movie_preview.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | void main() { 8 | group('MoviePreview -', () { 9 | final movieWithImage = Movie( 10 | id: 'fake_id', 11 | title: 'fake_title', 12 | releaseDate: DateTime(1990, 10, 20), 13 | imageUrl: 'fake_image_url', 14 | ); 15 | 16 | final movieWithoutImage = Movie( 17 | id: 'fake_id', 18 | title: 'fake_title', 19 | releaseDate: DateTime(1990, 10, 20), 20 | ); 21 | 22 | Widget buildTestableWidget(Widget widget) { 23 | return MaterialApp( 24 | home: widget, 25 | ); 26 | } 27 | 28 | group('when widget is shown', () { 29 | group('given Move has an image', () { 30 | testWidgets('should show the image', (WidgetTester tester) async { 31 | const key = Key('image_key'); 32 | await tester.pumpWidget( 33 | buildTestableWidget( 34 | MoviePreview( 35 | movie: movieWithImage, 36 | imageKey: key, 37 | ), 38 | ), 39 | ); 40 | final finder = find.byKey(key); 41 | expect(finder, findsOneWidget); 42 | }); 43 | }); 44 | 45 | group('given Move has no image', () { 46 | testWidgets('should not show the image container', 47 | (WidgetTester tester) async { 48 | const key = Key('image_key'); 49 | await tester.pumpWidget( 50 | buildTestableWidget( 51 | MoviePreview( 52 | movie: movieWithoutImage, 53 | imageKey: key, 54 | ), 55 | ), 56 | ); 57 | final finder = find.byKey(key); 58 | expect(finder, findsNothing); 59 | }); 60 | }); 61 | 62 | testWidgets('should show the Movie title', (WidgetTester tester) async { 63 | await tester.pumpWidget( 64 | buildTestableWidget( 65 | MoviePreview( 66 | movie: movieWithoutImage, 67 | ), 68 | ), 69 | ); 70 | final finder = find.text(movieWithoutImage.title); 71 | expect(finder, findsOneWidget); 72 | }); 73 | 74 | testWidgets('should show the Movie release date', 75 | (WidgetTester tester) async { 76 | await tester.pumpWidget( 77 | buildTestableWidget( 78 | MoviePreview( 79 | movie: movieWithoutImage, 80 | ), 81 | ), 82 | ); 83 | final finder = find.text('Oct 20, 1990'); 84 | expect(finder, findsOneWidget); 85 | }); 86 | }); 87 | 88 | group('when tap on the widget', () { 89 | testWidgets('should navigate to MovieDetailScreen', 90 | (WidgetTester tester) async { 91 | await tester.pumpWidget( 92 | buildTestableWidget( 93 | MoviePreview( 94 | movie: movieWithoutImage, 95 | ), 96 | ), 97 | ); 98 | final finder = find.byType(GestureDetector); 99 | await tester.tap(finder); 100 | await tester.pumpAndSettle(); 101 | 102 | expect(find.byType(MovieDetailScreen), findsOneWidget); 103 | }); 104 | }); 105 | }); 106 | } 107 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_movies_app/data/database/dao/movies_dao.dart'; 7 | import 'package:flutter_movies_app/data/database/database_mapper.dart'; 8 | import 'package:flutter_movies_app/data/datasource/preferences.dart'; 9 | import 'package:flutter_movies_app/data/network/client/api_client.dart'; 10 | import 'package:flutter_movies_app/data/network/network_mapper.dart'; 11 | import 'package:flutter_movies_app/data/repository/movies_repository.dart'; 12 | import 'package:flutter_movies_app/data/repository/preferences_repository.dart'; 13 | import 'package:flutter_movies_app/domain/model/config.dart'; 14 | import 'package:flutter_movies_app/domain/service/app_service.dart'; 15 | import 'package:flutter_movies_app/presentation/app/app.dart'; 16 | import 'package:logger/logger.dart'; 17 | import 'package:provider/provider.dart'; 18 | import 'package:provider/single_child_widget.dart'; 19 | import 'package:shared_preferences/shared_preferences.dart'; 20 | import 'package:sqflite/sqflite.dart'; 21 | 22 | class InitialData { 23 | final List providers; 24 | 25 | InitialData({required this.providers}); 26 | } 27 | 28 | Future main() async { 29 | WidgetsFlutterBinding.ensureInitialized(); 30 | 31 | // ignore: avoid_redundant_argument_values, deprecated_member_use 32 | await Sqflite.devSetDebugModeOn(kDebugMode); 33 | 34 | final data = await _createData(); 35 | 36 | runApp(App(data: data)); 37 | } 38 | 39 | Future _createData() async { 40 | // Util 41 | final log = Logger( 42 | printer: PrettyPrinter(), 43 | level: kDebugMode ? Level.trace : Level.off, 44 | ); 45 | 46 | // Load project configuration 47 | final config = await _loadConfig(log); 48 | 49 | // Data 50 | final apiClient = ApiClient( 51 | baseUrl: 'https://moviesdatabase.p.rapidapi.com/', 52 | apiKey: config.apiKey, 53 | apiHost: config.apiHost, 54 | ); 55 | final networkMapper = NetworkMapper(log: log); 56 | final moviesDao = MoviesDao(); 57 | final databaseMapper = DatabaseMapper(log: log); 58 | final moviesRepository = MoviesRepository( 59 | apiClient: apiClient, 60 | networkMapper: networkMapper, 61 | moviesDao: moviesDao, 62 | databaseMapper: databaseMapper, 63 | ); 64 | 65 | final preferences = Preferences(prefs: await SharedPreferences.getInstance()); 66 | final preferencesRepo = PreferencesRepository(preferences: preferences); 67 | 68 | // Services 69 | final appService = AppService(preferencesRepo: preferencesRepo); 70 | await appService.load(); 71 | 72 | // Create list of providers 73 | return InitialData( 74 | providers: [ 75 | Provider.value(value: log), 76 | Provider.value(value: moviesRepository), 77 | ChangeNotifierProvider.value(value: appService) 78 | ], 79 | ); 80 | } 81 | 82 | Future _loadConfig(Logger log) async { 83 | String raw; 84 | 85 | try { 86 | raw = await rootBundle.loadString('assets/config/config.json'); 87 | 88 | final config = json.decode(raw) as Map; 89 | 90 | return Config( 91 | apiKey: config['apiKey'] as String, 92 | apiHost: config['apiHost'] as String, 93 | ); 94 | } catch (e) { 95 | log.e( 96 | 'Error while loading project configuration, please make sure ' 97 | 'that the file located at /assets/config/config.json ' 98 | 'exists and that it contains the correct configuration.', 99 | error: e, 100 | ); 101 | rethrow; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/presentation/list/movie_preview.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_movies_app/domain/model/movie.dart'; 4 | import 'package:flutter_movies_app/presentation/detail/movie_detail_screen.dart'; 5 | import 'package:intl/intl.dart'; 6 | 7 | class MoviePreview extends StatelessWidget { 8 | static const _size = 100.0; 9 | 10 | final Movie movie; 11 | final Key? imageKey; 12 | 13 | const MoviePreview({ 14 | required this.movie, 15 | this.imageKey, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final DateFormat formatter = DateFormat.yMMMd(); 21 | 22 | return GestureDetector( 23 | onTap: () => Navigator.of(context).push( 24 | MaterialPageRoute( 25 | builder: (context) => MovieDetailScreen(movie: movie), 26 | ), 27 | ), 28 | child: Card( 29 | child: Row( 30 | children: [ 31 | // Movie image 32 | if (movie.imageUrl != null) 33 | Container( 34 | key: imageKey, 35 | padding: const EdgeInsets.only( 36 | left: 8.0, 37 | top: 8.0, 38 | right: 16.0, 39 | bottom: 8.0, 40 | ), 41 | child: SizedBox( 42 | width: _size, 43 | height: _size, 44 | child: ClipRRect( 45 | borderRadius: BorderRadius.circular(5.0), 46 | child: CachedNetworkImage( 47 | imageUrl: movie.imageUrl!, 48 | ), 49 | ), 50 | ), 51 | ) 52 | else 53 | Container( 54 | padding: const EdgeInsets.only( 55 | left: 8.0, 56 | top: 8.0, 57 | right: 16.0, 58 | bottom: 8.0, 59 | ), 60 | child: const _PlaceholderImage( 61 | size: _size, 62 | ), 63 | ), 64 | // Movie title 65 | Flexible( 66 | child: Container( 67 | padding: const EdgeInsets.only( 68 | left: 16.0, 69 | top: 12.0, 70 | right: 16.0, 71 | bottom: 12.0, 72 | ), 73 | child: Center( 74 | child: Column( 75 | mainAxisSize: MainAxisSize.min, 76 | children: [ 77 | Text( 78 | movie.title, 79 | style: Theme.of(context).textTheme.titleLarge, 80 | overflow: TextOverflow.ellipsis, 81 | ), 82 | Padding( 83 | padding: const EdgeInsets.only(top: 12.0), 84 | child: Text( 85 | formatter.format(movie.releaseDate), 86 | style: Theme.of(context).textTheme.bodySmall, 87 | ), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ), 93 | ) 94 | ], 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | 101 | class _PlaceholderImage extends StatelessWidget { 102 | final double size; 103 | 104 | const _PlaceholderImage({required this.size}); 105 | 106 | @override 107 | Widget build(BuildContext context) => Icon( 108 | Icons.image_not_supported, 109 | size: size, 110 | color: Colors.black38, 111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /lib/presentation/list/movies_list_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_movies_app/data/repository/movies_repository.dart'; 3 | import 'package:flutter_movies_app/domain/model/movie.dart'; 4 | import 'package:flutter_movies_app/domain/service/app_service.dart'; 5 | import 'package:flutter_movies_app/presentation/list/movie_preview.dart'; 6 | import 'package:flutter_movies_app/presentation/list/movies_list_model.dart'; 7 | import 'package:flutter_movies_app/util/l10n/app_localizations.dart'; 8 | import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; 9 | import 'package:logger/logger.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class MoviesListScreen extends StatefulWidget { 13 | @override 14 | State createState() => _MoviesListScreenState(); 15 | } 16 | 17 | class _MoviesListScreenState extends State { 18 | late final AppService _appService; 19 | 20 | late final MoviesListModel _model; 21 | final PagingController _pagingController = 22 | PagingController(firstPageKey: 1); 23 | late final Future _future; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | 29 | _appService = Provider.of(context, listen: false); 30 | 31 | _model = MoviesListModel( 32 | log: Provider.of(context, listen: false), 33 | moviesRepo: Provider.of(context, listen: false), 34 | ); 35 | 36 | _future = _checkNewData(); 37 | 38 | _pagingController.addPageRequestListener((pageKey) async { 39 | try { 40 | final movies = await _model.fetchPage(pageKey); 41 | _pagingController.appendPage(movies, pageKey + 1); 42 | } catch (e) { 43 | _pagingController.error = e; 44 | } 45 | }); 46 | } 47 | 48 | @override 49 | void dispose() { 50 | _pagingController.dispose(); 51 | super.dispose(); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | appBar: AppBar( 58 | title: Text(AppLocalizations.of(context).upcomingMovies), 59 | actions: [ 60 | IconButton( 61 | onPressed: () { 62 | _appService.themeMode = _appService.themeMode == ThemeMode.light 63 | ? ThemeMode.dark 64 | : ThemeMode.light; 65 | }, 66 | tooltip: AppLocalizations.of(context).toggleLightDart, 67 | icon: Icon( 68 | _appService.themeMode == ThemeMode.light 69 | ? Icons.dark_mode 70 | : Icons.light_mode, 71 | ), 72 | ), 73 | ], 74 | ), 75 | body: FutureBuilder( 76 | future: _future, 77 | builder: (context, snapshot) => RefreshIndicator( 78 | onRefresh: _refresh, 79 | child: PagedListView( 80 | pagingController: _pagingController, 81 | builderDelegate: PagedChildBuilderDelegate( 82 | itemBuilder: (context, movie, index) => Container( 83 | padding: const EdgeInsets.only( 84 | left: 12.0, 85 | top: 6.0, 86 | right: 12.0, 87 | bottom: 6.0, 88 | ), 89 | child: MoviePreview(movie: movie), 90 | ), 91 | ), 92 | ), 93 | ), 94 | ), 95 | ); 96 | } 97 | 98 | Future _refresh() async { 99 | await _model.deletePersistedMovies(); 100 | _pagingController.refresh(); 101 | } 102 | 103 | Future _checkNewData() async { 104 | WidgetsBinding.instance.addPostFrameCallback((_) async { 105 | final scaffoldMessenger = ScaffoldMessenger.of(context); 106 | final appLocalizations = AppLocalizations.of(context); 107 | final hasNewData = await _model.hasNewData(); 108 | 109 | if (hasNewData) { 110 | scaffoldMessenger.showSnackBar( 111 | SnackBar( 112 | content: Text(appLocalizations.getNewData), 113 | action: SnackBarAction( 114 | label: appLocalizations.refresh, 115 | onPressed: _refresh, 116 | ), 117 | ), 118 | ); 119 | } 120 | }); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/presentation/detail/movie_detail_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_movies_app/domain/model/movie.dart'; 4 | 5 | class MovieDetailScreen extends StatelessWidget { 6 | final Movie movie; 7 | 8 | const MovieDetailScreen({required this.movie}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | body: CustomScrollView( 14 | slivers: [ 15 | SliverAppBar( 16 | pinned: true, 17 | expandedHeight: movie.imageUrl != null ? 250.0 : null, 18 | flexibleSpace: FlexibleSpaceBar( 19 | title: Text(movie.title), 20 | background: movie.imageUrl != null 21 | ? CachedNetworkImage( 22 | imageUrl: movie.imageUrl!, 23 | ) 24 | : null, 25 | ), 26 | ), 27 | SliverList( 28 | delegate: SliverChildListDelegate( 29 | [ 30 | Container( 31 | padding: const EdgeInsets.only( 32 | left: 24.0, 33 | top: 12.0, 34 | right: 24.0, 35 | bottom: 24.0, 36 | ), 37 | child: const Text( 38 | ''' 39 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed id massa non mauris ultrices tincidunt. Pellentesque finibus consectetur suscipit. Suspendisse ut consectetur eros. Nullam ac felis nisl. Aliquam eleifend ligula id bibendum cursus. Phasellus egestas sem a tortor tincidunt, nec aliquet diam cursus. Sed sodales rhoncus nunc in vehicula. Quisque pulvinar velit massa, ac tincidunt purus volutpat in. Aliquam convallis, nunc quis luctus aliquet, velit quam consequat libero, vulputate commodo lacus nisl sit amet nunc. Sed lacinia ex purus, nec tristique metus bibendum ac. Integer ut ante in dui luctus accumsan ac id turpis. Duis bibendum tempor sem. In hac habitasse platea dictumst. Suspendisse libero orci, accumsan ac velit convallis, consectetur viverra ligula. 40 | 41 | Aliquam iaculis lorem ac neque iaculis consectetur. Integer volutpat elit eu mi blandit, id ultrices leo congue. Donec eros turpis, iaculis sit amet maximus at, eleifend et felis. Nullam efficitur rhoncus bibendum. Sed imperdiet, nisl varius auctor imperdiet, ex ipsum cursus ipsum, non gravida odio nibh vitae augue. Phasellus sit amet suscipit ante. Vestibulum in lacus in nibh iaculis iaculis. Nulla in mauris vel ante blandit molestie sed eget leo. Donec id magna ac ex fermentum posuere. Ut vitae massa vehicula, facilisis sem ultricies, finibus dui. Nunc eleifend cursus aliquam. Nullam sed odio in neque faucibus finibus. 42 | 43 | In hac habitasse platea dictumst. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer vitae convallis neque. Curabitur ultrices varius leo. Aliquam quis congue lacus. Pellentesque eget varius ipsum. Nam sed nulla in justo pretium dictum. Vivamus ut ligula nisl. Morbi volutpat vehicula diam, euismod dapibus sapien faucibus ac. Duis nunc mi, accumsan blandit ante ac, laoreet tristique mi. Nulla id velit tincidunt, volutpat est vel, aliquet ligula. 44 | 45 | Sed consequat nulla enim, in tincidunt lacus laoreet id. Maecenas lacus est, venenatis id feugiat id, fringilla ac odio. Nullam sollicitudin, augue eu dictum egestas, sem orci venenatis eros, nec tempus ante nibh quis velit. Morbi sit amet purus nibh. Donec ut nulla vel libero cursus eleifend in eu nisl. Vivamus mollis, ipsum eu vestibulum tincidunt, sem elit pharetra orci, vitae hendrerit ligula velit in mauris. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed lobortis elit id dapibus tincidunt. Duis posuere, risus quis cursus viverra, ligula sapien dictum dui, sit amet ultrices est justo feugiat dolor. Quisque congue feugiat leo, a mattis odio bibendum vel. 46 | 47 | Morbi dignissim, purus a rhoncus iaculis, quam urna tincidunt metus, et congue sem quam eu nisi. Donec et congue velit. Maecenas efficitur augue et enim ultrices, ac feugiat purus gravida. Integer quis arcu ut neque auctor dapibus non vel velit. Quisque condimentum dolor lectus, ut euismod lorem blandit ac. Nullam et fringilla purus. Vestibulum volutpat tempor velit, fringilla elementum sem tristique condimentum. Donec ac sapien vitae ex malesuada tincidunt. Nunc lacus magna, lobortis vitae malesuada ut, accumsan nec massa. Ut porta lacus ac turpis dapibus tincidunt. Etiam cursus, arcu nec auctor pulvinar, sapien nibh rutrum tellus, vitae eleifend ipsum massa vel tortor. Aenean suscipit auctor est id gravida. Ut malesuada commodo nulla quis tempor. Donec lobortis turpis eu enim vehicula, eget commodo quam finibus. Mauris ut nulla rhoncus, malesuada justo quis, scelerisque erat. 48 | ''', 49 | ), 50 | ) 51 | ], 52 | ), 53 | ), 54 | ], 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/data/network/entity/movie_entity.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'movie_entity.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | UpcomingMovies _$UpcomingMoviesFromJson(Map json) => 10 | UpcomingMovies( 11 | next: json['next'] as String, 12 | entries: json['entries'] as int, 13 | results: (json['results'] as List) 14 | .map((e) => MovieEntity.fromJson(e as Map)) 15 | .toList(), 16 | ); 17 | 18 | Map _$UpcomingMoviesToJson(UpcomingMovies instance) => 19 | { 20 | 'next': instance.next, 21 | 'entries': instance.entries, 22 | 'results': instance.results, 23 | }; 24 | 25 | MovieEntity _$MovieEntityFromJson(Map json) => MovieEntity( 26 | id: json['id'] as String, 27 | primaryImage: json['primaryImage'] == null 28 | ? null 29 | : PrimaryImage.fromJson(json['primaryImage'] as Map), 30 | titleType: TitleType.fromJson(json['titleType'] as Map), 31 | titleText: TitleText.fromJson(json['titleText'] as Map), 32 | releaseYear: json['releaseYear'] == null 33 | ? null 34 | : ReleaseYear.fromJson(json['releaseYear'] as Map), 35 | releaseDate: 36 | ReleaseDate.fromJson(json['releaseDate'] as Map), 37 | ); 38 | 39 | Map _$MovieEntityToJson(MovieEntity instance) => 40 | { 41 | 'id': instance.id, 42 | 'primaryImage': instance.primaryImage, 43 | 'titleType': instance.titleType, 44 | 'titleText': instance.titleText, 45 | 'releaseYear': instance.releaseYear, 46 | 'releaseDate': instance.releaseDate, 47 | }; 48 | 49 | PrimaryImage _$PrimaryImageFromJson(Map json) => PrimaryImage( 50 | id: json['id'] as String, 51 | width: json['width'] as int, 52 | height: json['height'] as int, 53 | url: json['url'] as String, 54 | caption: Caption.fromJson(json['caption'] as Map), 55 | ); 56 | 57 | Map _$PrimaryImageToJson(PrimaryImage instance) => 58 | { 59 | 'id': instance.id, 60 | 'width': instance.width, 61 | 'height': instance.height, 62 | 'url': instance.url, 63 | 'caption': instance.caption, 64 | }; 65 | 66 | Caption _$CaptionFromJson(Map json) => Caption( 67 | plainText: json['plainText'] as String, 68 | ); 69 | 70 | Map _$CaptionToJson(Caption instance) => { 71 | 'plainText': instance.plainText, 72 | }; 73 | 74 | ReleaseDate _$ReleaseDateFromJson(Map json) => ReleaseDate( 75 | day: json['day'] as int, 76 | month: json['month'] as int, 77 | year: json['year'] as int, 78 | ); 79 | 80 | Map _$ReleaseDateToJson(ReleaseDate instance) => 81 | { 82 | 'day': instance.day, 83 | 'month': instance.month, 84 | 'year': instance.year, 85 | }; 86 | 87 | ReleaseYear _$ReleaseYearFromJson(Map json) => ReleaseYear( 88 | year: json['year'] as int, 89 | endYear: json['endYear'], 90 | ); 91 | 92 | Map _$ReleaseYearToJson(ReleaseYear instance) => 93 | { 94 | 'year': instance.year, 95 | 'endYear': instance.endYear, 96 | }; 97 | 98 | TitleText _$TitleTextFromJson(Map json) => TitleText( 99 | text: json['text'] as String, 100 | ); 101 | 102 | Map _$TitleTextToJson(TitleText instance) => { 103 | 'text': instance.text, 104 | }; 105 | 106 | TitleType _$TitleTypeFromJson(Map json) => TitleType( 107 | text: json['text'] as String, 108 | id: json['id'] as String, 109 | isSeries: json['isSeries'] as bool, 110 | isEpisode: json['isEpisode'] as bool, 111 | categories: (json['categories'] as List?) 112 | ?.map((e) => Category.fromJson(e as Map)) 113 | .toList(), 114 | canHaveEpisodes: json['canHaveEpisodes'] as bool?, 115 | ); 116 | 117 | Map _$TitleTypeToJson(TitleType instance) => { 118 | 'text': instance.text, 119 | 'id': instance.id, 120 | 'isSeries': instance.isSeries, 121 | 'isEpisode': instance.isEpisode, 122 | 'categories': instance.categories, 123 | 'canHaveEpisodes': instance.canHaveEpisodes, 124 | }; 125 | 126 | Category _$CategoryFromJson(Map json) => Category( 127 | value: json['value'] as String, 128 | ); 129 | 130 | Map _$CategoryToJson(Category instance) => { 131 | 'value': instance.value, 132 | }; 133 | -------------------------------------------------------------------------------- /lib/domain/model/config.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'config.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 16 | 17 | /// @nodoc 18 | mixin _$Config { 19 | String get apiKey => throw _privateConstructorUsedError; 20 | String get apiHost => throw _privateConstructorUsedError; 21 | 22 | @JsonKey(ignore: true) 23 | $ConfigCopyWith get copyWith => throw _privateConstructorUsedError; 24 | } 25 | 26 | /// @nodoc 27 | abstract class $ConfigCopyWith<$Res> { 28 | factory $ConfigCopyWith(Config value, $Res Function(Config) then) = 29 | _$ConfigCopyWithImpl<$Res, Config>; 30 | @useResult 31 | $Res call({String apiKey, String apiHost}); 32 | } 33 | 34 | /// @nodoc 35 | class _$ConfigCopyWithImpl<$Res, $Val extends Config> 36 | implements $ConfigCopyWith<$Res> { 37 | _$ConfigCopyWithImpl(this._value, this._then); 38 | 39 | // ignore: unused_field 40 | final $Val _value; 41 | // ignore: unused_field 42 | final $Res Function($Val) _then; 43 | 44 | @pragma('vm:prefer-inline') 45 | @override 46 | $Res call({ 47 | Object? apiKey = null, 48 | Object? apiHost = null, 49 | }) { 50 | return _then(_value.copyWith( 51 | apiKey: null == apiKey 52 | ? _value.apiKey 53 | : apiKey // ignore: cast_nullable_to_non_nullable 54 | as String, 55 | apiHost: null == apiHost 56 | ? _value.apiHost 57 | : apiHost // ignore: cast_nullable_to_non_nullable 58 | as String, 59 | ) as $Val); 60 | } 61 | } 62 | 63 | /// @nodoc 64 | abstract class _$$_ConfigCopyWith<$Res> implements $ConfigCopyWith<$Res> { 65 | factory _$$_ConfigCopyWith(_$_Config value, $Res Function(_$_Config) then) = 66 | __$$_ConfigCopyWithImpl<$Res>; 67 | @override 68 | @useResult 69 | $Res call({String apiKey, String apiHost}); 70 | } 71 | 72 | /// @nodoc 73 | class __$$_ConfigCopyWithImpl<$Res> 74 | extends _$ConfigCopyWithImpl<$Res, _$_Config> 75 | implements _$$_ConfigCopyWith<$Res> { 76 | __$$_ConfigCopyWithImpl(_$_Config _value, $Res Function(_$_Config) _then) 77 | : super(_value, _then); 78 | 79 | @pragma('vm:prefer-inline') 80 | @override 81 | $Res call({ 82 | Object? apiKey = null, 83 | Object? apiHost = null, 84 | }) { 85 | return _then(_$_Config( 86 | apiKey: null == apiKey 87 | ? _value.apiKey 88 | : apiKey // ignore: cast_nullable_to_non_nullable 89 | as String, 90 | apiHost: null == apiHost 91 | ? _value.apiHost 92 | : apiHost // ignore: cast_nullable_to_non_nullable 93 | as String, 94 | )); 95 | } 96 | } 97 | 98 | /// @nodoc 99 | 100 | class _$_Config implements _Config { 101 | const _$_Config({required this.apiKey, required this.apiHost}); 102 | 103 | @override 104 | final String apiKey; 105 | @override 106 | final String apiHost; 107 | 108 | @override 109 | String toString() { 110 | return 'Config(apiKey: $apiKey, apiHost: $apiHost)'; 111 | } 112 | 113 | @override 114 | bool operator ==(dynamic other) { 115 | return identical(this, other) || 116 | (other.runtimeType == runtimeType && 117 | other is _$_Config && 118 | (identical(other.apiKey, apiKey) || other.apiKey == apiKey) && 119 | (identical(other.apiHost, apiHost) || other.apiHost == apiHost)); 120 | } 121 | 122 | @override 123 | int get hashCode => Object.hash(runtimeType, apiKey, apiHost); 124 | 125 | @JsonKey(ignore: true) 126 | @override 127 | @pragma('vm:prefer-inline') 128 | _$$_ConfigCopyWith<_$_Config> get copyWith => 129 | __$$_ConfigCopyWithImpl<_$_Config>(this, _$identity); 130 | } 131 | 132 | abstract class _Config implements Config { 133 | const factory _Config( 134 | {required final String apiKey, 135 | required final String apiHost}) = _$_Config; 136 | 137 | @override 138 | String get apiKey; 139 | @override 140 | String get apiHost; 141 | @override 142 | @JsonKey(ignore: true) 143 | _$$_ConfigCopyWith<_$_Config> get copyWith => 144 | throw _privateConstructorUsedError; 145 | } 146 | -------------------------------------------------------------------------------- /lib/util/l10n/app_localizations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | import 'package:intl/intl.dart' as intl; 7 | 8 | import 'app_localizations_en.dart'; 9 | import 'app_localizations_es.dart'; 10 | 11 | /// Callers can lookup localized strings with an instance of AppLocalizations 12 | /// returned by `AppLocalizations.of(context)`. 13 | /// 14 | /// Applications need to include `AppLocalizations.delegate()` in their app's 15 | /// `localizationDelegates` list, and the locales they support in the app's 16 | /// `supportedLocales` list. For example: 17 | /// 18 | /// ```dart 19 | /// import 'l10n/app_localizations.dart'; 20 | /// 21 | /// return MaterialApp( 22 | /// localizationsDelegates: AppLocalizations.localizationsDelegates, 23 | /// supportedLocales: AppLocalizations.supportedLocales, 24 | /// home: MyApplicationHome(), 25 | /// ); 26 | /// ``` 27 | /// 28 | /// ## Update pubspec.yaml 29 | /// 30 | /// Please make sure to update your pubspec.yaml to include the following 31 | /// packages: 32 | /// 33 | /// ```yaml 34 | /// dependencies: 35 | /// # Internationalization support. 36 | /// flutter_localizations: 37 | /// sdk: flutter 38 | /// intl: any # Use the pinned version from flutter_localizations 39 | /// 40 | /// # Rest of dependencies 41 | /// ``` 42 | /// 43 | /// ## iOS Applications 44 | /// 45 | /// iOS applications define key application metadata, including supported 46 | /// locales, in an Info.plist file that is built into the application bundle. 47 | /// To configure the locales supported by your app, you’ll need to edit this 48 | /// file. 49 | /// 50 | /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. 51 | /// Then, in the Project Navigator, open the Info.plist file under the Runner 52 | /// project’s Runner folder. 53 | /// 54 | /// Next, select the Information Property List item, select Add Item from the 55 | /// Editor menu, then select Localizations from the pop-up menu. 56 | /// 57 | /// Select and expand the newly-created Localizations item then, for each 58 | /// locale your application supports, add a new item and select the locale 59 | /// you wish to add from the pop-up menu in the Value field. This list should 60 | /// be consistent with the languages listed in the AppLocalizations.supportedLocales 61 | /// property. 62 | abstract class AppLocalizations { 63 | AppLocalizations(String locale) 64 | : localeName = intl.Intl.canonicalizedLocale(locale.toString()); 65 | 66 | final String localeName; 67 | 68 | static AppLocalizations of(BuildContext context) { 69 | return Localizations.of(context, AppLocalizations)!; 70 | } 71 | 72 | static const LocalizationsDelegate delegate = 73 | _AppLocalizationsDelegate(); 74 | 75 | /// A list of this localizations delegate along with the default localizations 76 | /// delegates. 77 | /// 78 | /// Returns a list of localizations delegates containing this delegate along with 79 | /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, 80 | /// and GlobalWidgetsLocalizations.delegate. 81 | /// 82 | /// Additional delegates can be added by appending to this list in 83 | /// MaterialApp. This list does not have to be used at all if a custom list 84 | /// of delegates is preferred or required. 85 | static const List> localizationsDelegates = 86 | >[ 87 | delegate, 88 | GlobalMaterialLocalizations.delegate, 89 | GlobalCupertinoLocalizations.delegate, 90 | GlobalWidgetsLocalizations.delegate, 91 | ]; 92 | 93 | /// A list of this localizations delegate's supported locales. 94 | static const List supportedLocales = [ 95 | Locale('en'), 96 | Locale('es') 97 | ]; 98 | 99 | /// No description provided for @appName. 100 | /// 101 | /// In en, this message translates to: 102 | /// **'Movies App'** 103 | String get appName; 104 | 105 | /// No description provided for @toggleLightDart. 106 | /// 107 | /// In en, this message translates to: 108 | /// **'Toggle light/dark mode'** 109 | String get toggleLightDart; 110 | 111 | /// No description provided for @upcomingMovies. 112 | /// 113 | /// In en, this message translates to: 114 | /// **'Upcoming movies'** 115 | String get upcomingMovies; 116 | 117 | /// No description provided for @refresh. 118 | /// 119 | /// In en, this message translates to: 120 | /// **'Refresh'** 121 | String get refresh; 122 | 123 | /// No description provided for @getNewData. 124 | /// 125 | /// In en, this message translates to: 126 | /// **'Refresh to fetch new data'** 127 | String get getNewData; 128 | } 129 | 130 | class _AppLocalizationsDelegate 131 | extends LocalizationsDelegate { 132 | const _AppLocalizationsDelegate(); 133 | 134 | @override 135 | Future load(Locale locale) { 136 | return SynchronousFuture(lookupAppLocalizations(locale)); 137 | } 138 | 139 | @override 140 | bool isSupported(Locale locale) => 141 | ['en', 'es'].contains(locale.languageCode); 142 | 143 | @override 144 | bool shouldReload(_AppLocalizationsDelegate old) => false; 145 | } 146 | 147 | AppLocalizations lookupAppLocalizations(Locale locale) { 148 | // Lookup logic when only language code is specified. 149 | switch (locale.languageCode) { 150 | case 'en': 151 | return AppLocalizationsEn(); 152 | case 'es': 153 | return AppLocalizationsEs(); 154 | } 155 | 156 | throw FlutterError( 157 | 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 158 | 'an issue with the localizations generation tool. Please file an issue ' 159 | 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 160 | 'that was used.'); 161 | } 162 | -------------------------------------------------------------------------------- /test/data/repository/movies_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_movies_app/data/database/dao/movies_dao.dart'; 2 | import 'package:flutter_movies_app/data/database/database_mapper.dart'; 3 | import 'package:flutter_movies_app/data/database/entity/movie_db_entity.dart'; 4 | import 'package:flutter_movies_app/data/network/client/api_client.dart'; 5 | import 'package:flutter_movies_app/data/network/entity/movie_entity.dart'; 6 | import 'package:flutter_movies_app/data/network/network_mapper.dart'; 7 | import 'package:flutter_movies_app/data/repository/movies_repository.dart'; 8 | import 'package:flutter_movies_app/domain/model/movie.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:mocktail/mocktail.dart'; 11 | 12 | import '../../mocks.dart'; 13 | 14 | void main() { 15 | group('MoviesRepository -', () { 16 | late MoviesRepository moviesRepo; 17 | 18 | late ApiClient apiClient; 19 | late NetworkMapper networkMapper; 20 | late MoviesDao moviesDao; 21 | late DatabaseMapper databaseMapper; 22 | 23 | final today = DateTime.now(); 24 | 25 | final databaseData = [ 26 | MovieDbEntity( 27 | id: 0, 28 | movieId: 'movie_0', 29 | title: 'title_0', 30 | releaseDate: today.millisecondsSinceEpoch, 31 | ), 32 | MovieDbEntity( 33 | id: 1, 34 | movieId: 'movie_1', 35 | title: 'title_1', 36 | releaseDate: today.millisecondsSinceEpoch, 37 | ), 38 | MovieDbEntity( 39 | id: 2, 40 | movieId: 'movie_2', 41 | title: 'title_2', 42 | releaseDate: today.millisecondsSinceEpoch, 43 | ), 44 | ]; 45 | 46 | final apiData = [ 47 | MovieEntity( 48 | id: 'movie_0', 49 | titleType: TitleType( 50 | id: 'title_type_0', 51 | text: 'title_0', 52 | isSeries: false, 53 | isEpisode: false, 54 | ), 55 | titleText: TitleText( 56 | text: 'title_0', 57 | ), 58 | releaseDate: 59 | ReleaseDate(day: today.day, month: today.month, year: today.year), 60 | ), 61 | MovieEntity( 62 | id: 'movie_1', 63 | titleType: TitleType( 64 | id: 'title_type_1', 65 | text: 'title_1', 66 | isSeries: false, 67 | isEpisode: false, 68 | ), 69 | titleText: TitleText( 70 | text: 'title_1', 71 | ), 72 | releaseDate: 73 | ReleaseDate(day: today.day, month: today.month, year: today.year), 74 | ), 75 | MovieEntity( 76 | id: 'movie_2', 77 | titleType: TitleType( 78 | id: 'title_type_2', 79 | text: 'title_2', 80 | isSeries: false, 81 | isEpisode: false, 82 | ), 83 | titleText: TitleText( 84 | text: 'title_2', 85 | ), 86 | releaseDate: 87 | ReleaseDate(day: today.day, month: today.month, year: today.year), 88 | ), 89 | ]; 90 | 91 | final models = [ 92 | Movie( 93 | id: 'movie_0', 94 | title: 'title_0', 95 | releaseDate: today, 96 | ), 97 | Movie( 98 | id: 'movie_1', 99 | title: 'title_1', 100 | releaseDate: today, 101 | ), 102 | Movie( 103 | id: 'movie_2', 104 | title: 'title_2', 105 | releaseDate: today, 106 | ), 107 | ]; 108 | 109 | setUp(() { 110 | apiClient = ApiClientMock(); 111 | networkMapper = NetworkMapperMock(); 112 | moviesDao = MoviesDaoMock(); 113 | databaseMapper = DatabaseMapperMock(); 114 | 115 | moviesRepo = MoviesRepository( 116 | apiClient: apiClient, 117 | networkMapper: networkMapper, 118 | moviesDao: moviesDao, 119 | databaseMapper: databaseMapper, 120 | ); 121 | }); 122 | 123 | group('when calling getUpcomingMovies()', () { 124 | group('given that database contains data', () { 125 | setUp(() { 126 | when(() => moviesDao.selectAll(limit: 10, offset: 0)) 127 | .thenAnswer((_) => Future.value(databaseData)); 128 | when(() => databaseMapper.toMovies(databaseData)).thenReturn(models); 129 | }); 130 | 131 | test('should return database movies', () async { 132 | final movies = await moviesRepo.getUpcomingMovies(limit: 10, page: 1); 133 | expect(movies, models); 134 | }); 135 | }); 136 | 137 | group("given that database don't contains relevant data", () { 138 | late List movies; 139 | 140 | setUp(() async { 141 | when(() => moviesDao.selectAll(limit: 10, offset: 0)) 142 | .thenAnswer((_) => Future.value([])); 143 | when(() => apiClient.getUpcomingMovies(page: 1)).thenAnswer( 144 | (invocation) => Future.value( 145 | UpcomingMovies( 146 | next: 'next', 147 | entries: 10, 148 | results: apiData, 149 | ), 150 | ), 151 | ); 152 | when(() => networkMapper.toMovies(apiData)).thenReturn(models); 153 | when(() => databaseMapper.toMovieDbEntities(models)) 154 | .thenReturn(databaseData); 155 | when(() => moviesDao.insertAll(databaseData)) 156 | .thenAnswer((_) => Future.value()); 157 | 158 | movies = await moviesRepo.getUpcomingMovies(limit: 10, page: 1); 159 | }); 160 | 161 | test('should fetch movies from remote API', () { 162 | final captured = verify( 163 | () => apiClient.getUpcomingMovies( 164 | page: captureAny(named: 'page'), 165 | ), 166 | ).captured; 167 | 168 | expect(captured.first, 1); 169 | }); 170 | 171 | test('should persist fetched data to database', () { 172 | final captured = verify( 173 | () => moviesDao.insertAll(captureAny()), 174 | ).captured; 175 | 176 | expect(captured.first, databaseData); 177 | }); 178 | 179 | test('should return fetched data', () { 180 | expect(movies, models); 181 | }); 182 | }); 183 | }); 184 | }); 185 | } 186 | -------------------------------------------------------------------------------- /lib/domain/model/movie.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'movie.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 16 | 17 | /// @nodoc 18 | mixin _$Movie { 19 | String get id => throw _privateConstructorUsedError; 20 | String get title => throw _privateConstructorUsedError; 21 | String? get imageUrl => throw _privateConstructorUsedError; 22 | DateTime get releaseDate => throw _privateConstructorUsedError; 23 | 24 | @JsonKey(ignore: true) 25 | $MovieCopyWith get copyWith => throw _privateConstructorUsedError; 26 | } 27 | 28 | /// @nodoc 29 | abstract class $MovieCopyWith<$Res> { 30 | factory $MovieCopyWith(Movie value, $Res Function(Movie) then) = 31 | _$MovieCopyWithImpl<$Res, Movie>; 32 | @useResult 33 | $Res call({String id, String title, String? imageUrl, DateTime releaseDate}); 34 | } 35 | 36 | /// @nodoc 37 | class _$MovieCopyWithImpl<$Res, $Val extends Movie> 38 | implements $MovieCopyWith<$Res> { 39 | _$MovieCopyWithImpl(this._value, this._then); 40 | 41 | // ignore: unused_field 42 | final $Val _value; 43 | // ignore: unused_field 44 | final $Res Function($Val) _then; 45 | 46 | @pragma('vm:prefer-inline') 47 | @override 48 | $Res call({ 49 | Object? id = null, 50 | Object? title = null, 51 | Object? imageUrl = freezed, 52 | Object? releaseDate = null, 53 | }) { 54 | return _then(_value.copyWith( 55 | id: null == id 56 | ? _value.id 57 | : id // ignore: cast_nullable_to_non_nullable 58 | as String, 59 | title: null == title 60 | ? _value.title 61 | : title // ignore: cast_nullable_to_non_nullable 62 | as String, 63 | imageUrl: freezed == imageUrl 64 | ? _value.imageUrl 65 | : imageUrl // ignore: cast_nullable_to_non_nullable 66 | as String?, 67 | releaseDate: null == releaseDate 68 | ? _value.releaseDate 69 | : releaseDate // ignore: cast_nullable_to_non_nullable 70 | as DateTime, 71 | ) as $Val); 72 | } 73 | } 74 | 75 | /// @nodoc 76 | abstract class _$$_MovieCopyWith<$Res> implements $MovieCopyWith<$Res> { 77 | factory _$$_MovieCopyWith(_$_Movie value, $Res Function(_$_Movie) then) = 78 | __$$_MovieCopyWithImpl<$Res>; 79 | @override 80 | @useResult 81 | $Res call({String id, String title, String? imageUrl, DateTime releaseDate}); 82 | } 83 | 84 | /// @nodoc 85 | class __$$_MovieCopyWithImpl<$Res> extends _$MovieCopyWithImpl<$Res, _$_Movie> 86 | implements _$$_MovieCopyWith<$Res> { 87 | __$$_MovieCopyWithImpl(_$_Movie _value, $Res Function(_$_Movie) _then) 88 | : super(_value, _then); 89 | 90 | @pragma('vm:prefer-inline') 91 | @override 92 | $Res call({ 93 | Object? id = null, 94 | Object? title = null, 95 | Object? imageUrl = freezed, 96 | Object? releaseDate = null, 97 | }) { 98 | return _then(_$_Movie( 99 | id: null == id 100 | ? _value.id 101 | : id // ignore: cast_nullable_to_non_nullable 102 | as String, 103 | title: null == title 104 | ? _value.title 105 | : title // ignore: cast_nullable_to_non_nullable 106 | as String, 107 | imageUrl: freezed == imageUrl 108 | ? _value.imageUrl 109 | : imageUrl // ignore: cast_nullable_to_non_nullable 110 | as String?, 111 | releaseDate: null == releaseDate 112 | ? _value.releaseDate 113 | : releaseDate // ignore: cast_nullable_to_non_nullable 114 | as DateTime, 115 | )); 116 | } 117 | } 118 | 119 | /// @nodoc 120 | 121 | class _$_Movie implements _Movie { 122 | const _$_Movie( 123 | {required this.id, 124 | required this.title, 125 | this.imageUrl, 126 | required this.releaseDate}); 127 | 128 | @override 129 | final String id; 130 | @override 131 | final String title; 132 | @override 133 | final String? imageUrl; 134 | @override 135 | final DateTime releaseDate; 136 | 137 | @override 138 | String toString() { 139 | return 'Movie(id: $id, title: $title, imageUrl: $imageUrl, releaseDate: $releaseDate)'; 140 | } 141 | 142 | @override 143 | bool operator ==(dynamic other) { 144 | return identical(this, other) || 145 | (other.runtimeType == runtimeType && 146 | other is _$_Movie && 147 | (identical(other.id, id) || other.id == id) && 148 | (identical(other.title, title) || other.title == title) && 149 | (identical(other.imageUrl, imageUrl) || 150 | other.imageUrl == imageUrl) && 151 | (identical(other.releaseDate, releaseDate) || 152 | other.releaseDate == releaseDate)); 153 | } 154 | 155 | @override 156 | int get hashCode => 157 | Object.hash(runtimeType, id, title, imageUrl, releaseDate); 158 | 159 | @JsonKey(ignore: true) 160 | @override 161 | @pragma('vm:prefer-inline') 162 | _$$_MovieCopyWith<_$_Movie> get copyWith => 163 | __$$_MovieCopyWithImpl<_$_Movie>(this, _$identity); 164 | } 165 | 166 | abstract class _Movie implements Movie { 167 | const factory _Movie( 168 | {required final String id, 169 | required final String title, 170 | final String? imageUrl, 171 | required final DateTime releaseDate}) = _$_Movie; 172 | 173 | @override 174 | String get id; 175 | @override 176 | String get title; 177 | @override 178 | String? get imageUrl; 179 | @override 180 | DateTime get releaseDate; 181 | @override 182 | @JsonKey(ignore: true) 183 | _$$_MovieCopyWith<_$_Movie> get copyWith => 184 | throw _privateConstructorUsedError; 185 | } 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 727CE4B47EC3886869B54788 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 910F56493263755CB0D15355 /* Pods_Runner.framework */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1AFD1B0F379953D6D6C250A9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 910F56493263755CB0D15355 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 963F81F53B7358C25423C129 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | C048AC89869FB1180E35A0B8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 727CE4B47EC3886869B54788 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 525BE5A25B33E4A75D646A7D /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 910F56493263755CB0D15355 /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 60B2AC47F5C3D77D36073A9C /* Pods */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | C048AC89869FB1180E35A0B8 /* Pods-Runner.debug.xcconfig */, 76 | 1AFD1B0F379953D6D6C250A9 /* Pods-Runner.release.xcconfig */, 77 | 963F81F53B7358C25423C129 /* Pods-Runner.profile.xcconfig */, 78 | ); 79 | name = Pods; 80 | path = Pods; 81 | sourceTree = ""; 82 | }; 83 | 9740EEB11CF90186004384FC /* Flutter */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 87 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 88 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 89 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 90 | ); 91 | name = Flutter; 92 | sourceTree = ""; 93 | }; 94 | 97C146E51CF9000F007C117D = { 95 | isa = PBXGroup; 96 | children = ( 97 | 9740EEB11CF90186004384FC /* Flutter */, 98 | 97C146F01CF9000F007C117D /* Runner */, 99 | 97C146EF1CF9000F007C117D /* Products */, 100 | 60B2AC47F5C3D77D36073A9C /* Pods */, 101 | 525BE5A25B33E4A75D646A7D /* Frameworks */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | 97C146EF1CF9000F007C117D /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146EE1CF9000F007C117D /* Runner.app */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | 97C146F01CF9000F007C117D /* Runner */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 117 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 118 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 119 | 97C147021CF9000F007C117D /* Info.plist */, 120 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 121 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 122 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 123 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 124 | ); 125 | path = Runner; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | A39F73E3412B714DBA1D7C6A /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | 38ECF6BA2AC7DA48B25B3952 /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = Runner; 149 | productName = Runner; 150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 97C146E61CF9000F007C117D /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 1300; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 38ECF6BA2AC7DA48B25B3952 /* [CP] Embed Pods Frameworks */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputFileListPaths = ( 207 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 208 | ); 209 | name = "[CP] Embed Pods Frameworks"; 210 | outputFileListPaths = ( 211 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 216 | showEnvVarsInLog = 0; 217 | }; 218 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 219 | isa = PBXShellScriptBuildPhase; 220 | alwaysOutOfDate = 1; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | inputPaths = ( 225 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 226 | ); 227 | name = "Thin Binary"; 228 | outputPaths = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 233 | }; 234 | 9740EEB61CF901F6004384FC /* Run Script */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | alwaysOutOfDate = 1; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | inputPaths = ( 241 | ); 242 | name = "Run Script"; 243 | outputPaths = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | shellPath = /bin/sh; 247 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 248 | }; 249 | A39F73E3412B714DBA1D7C6A /* [CP] Check Pods Manifest.lock */ = { 250 | isa = PBXShellScriptBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | inputFileListPaths = ( 255 | ); 256 | inputPaths = ( 257 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 258 | "${PODS_ROOT}/Manifest.lock", 259 | ); 260 | name = "[CP] Check Pods Manifest.lock"; 261 | outputFileListPaths = ( 262 | ); 263 | outputPaths = ( 264 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | shellPath = /bin/sh; 268 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 269 | showEnvVarsInLog = 0; 270 | }; 271 | /* End PBXShellScriptBuildPhase section */ 272 | 273 | /* Begin PBXSourcesBuildPhase section */ 274 | 97C146EA1CF9000F007C117D /* Sources */ = { 275 | isa = PBXSourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 279 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 97C146FB1CF9000F007C117D /* Base */, 290 | ); 291 | name = Main.storyboard; 292 | sourceTree = ""; 293 | }; 294 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | 97C147001CF9000F007C117D /* Base */, 298 | ); 299 | name = LaunchScreen.storyboard; 300 | sourceTree = ""; 301 | }; 302 | /* End PBXVariantGroup section */ 303 | 304 | /* Begin XCBuildConfiguration section */ 305 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 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-with-dsym"; 336 | ENABLE_NS_ASSERTIONS = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = iphoneos; 349 | SUPPORTED_PLATFORMS = iphoneos; 350 | TARGETED_DEVICE_FAMILY = "1,2"; 351 | VALIDATE_PRODUCT = YES; 352 | }; 353 | name = Profile; 354 | }; 355 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 356 | isa = XCBuildConfiguration; 357 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 358 | buildSettings = { 359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 360 | CLANG_ENABLE_MODULES = YES; 361 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 362 | DEVELOPMENT_TEAM = A97AY86M9N; 363 | ENABLE_BITCODE = NO; 364 | INFOPLIST_FILE = Runner/Info.plist; 365 | LD_RUNPATH_SEARCH_PATHS = ( 366 | "$(inherited)", 367 | "@executable_path/Frameworks", 368 | ); 369 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMoviesApp; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 372 | SWIFT_VERSION = 5.0; 373 | VERSIONING_SYSTEM = "apple-generic"; 374 | }; 375 | name = Profile; 376 | }; 377 | 97C147031CF9000F007C117D /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_COMMA = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 391 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 392 | CLANG_WARN_EMPTY_BODY = YES; 393 | CLANG_WARN_ENUM_CONVERSION = YES; 394 | CLANG_WARN_INFINITE_RECURSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 400 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 401 | CLANG_WARN_STRICT_PROTOTYPES = YES; 402 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 403 | CLANG_WARN_UNREACHABLE_CODE = YES; 404 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 405 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 406 | COPY_PHASE_STRIP = NO; 407 | DEBUG_INFORMATION_FORMAT = dwarf; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | ENABLE_TESTABILITY = YES; 410 | GCC_C_LANGUAGE_STANDARD = gnu99; 411 | GCC_DYNAMIC_NO_PIC = NO; 412 | GCC_NO_COMMON_BLOCKS = YES; 413 | GCC_OPTIMIZATION_LEVEL = 0; 414 | GCC_PREPROCESSOR_DEFINITIONS = ( 415 | "DEBUG=1", 416 | "$(inherited)", 417 | ); 418 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 419 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 420 | GCC_WARN_UNDECLARED_SELECTOR = YES; 421 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 422 | GCC_WARN_UNUSED_FUNCTION = YES; 423 | GCC_WARN_UNUSED_VARIABLE = YES; 424 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 425 | MTL_ENABLE_DEBUG_INFO = YES; 426 | ONLY_ACTIVE_ARCH = YES; 427 | SDKROOT = iphoneos; 428 | TARGETED_DEVICE_FAMILY = "1,2"; 429 | }; 430 | name = Debug; 431 | }; 432 | 97C147041CF9000F007C117D /* Release */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | ALWAYS_SEARCH_USER_PATHS = NO; 436 | CLANG_ANALYZER_NONNULL = YES; 437 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 438 | CLANG_CXX_LIBRARY = "libc++"; 439 | CLANG_ENABLE_MODULES = YES; 440 | CLANG_ENABLE_OBJC_ARC = YES; 441 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 442 | CLANG_WARN_BOOL_CONVERSION = YES; 443 | CLANG_WARN_COMMA = YES; 444 | CLANG_WARN_CONSTANT_CONVERSION = YES; 445 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 446 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 447 | CLANG_WARN_EMPTY_BODY = YES; 448 | CLANG_WARN_ENUM_CONVERSION = YES; 449 | CLANG_WARN_INFINITE_RECURSION = YES; 450 | CLANG_WARN_INT_CONVERSION = YES; 451 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 452 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 453 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 454 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 455 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 456 | CLANG_WARN_STRICT_PROTOTYPES = YES; 457 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 458 | CLANG_WARN_UNREACHABLE_CODE = YES; 459 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 460 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 461 | COPY_PHASE_STRIP = NO; 462 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 463 | ENABLE_NS_ASSERTIONS = NO; 464 | ENABLE_STRICT_OBJC_MSGSEND = YES; 465 | GCC_C_LANGUAGE_STANDARD = gnu99; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 468 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 469 | GCC_WARN_UNDECLARED_SELECTOR = YES; 470 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 471 | GCC_WARN_UNUSED_FUNCTION = YES; 472 | GCC_WARN_UNUSED_VARIABLE = YES; 473 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 474 | MTL_ENABLE_DEBUG_INFO = NO; 475 | SDKROOT = iphoneos; 476 | SUPPORTED_PLATFORMS = iphoneos; 477 | SWIFT_COMPILATION_MODE = wholemodule; 478 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | VALIDATE_PRODUCT = YES; 481 | }; 482 | name = Release; 483 | }; 484 | 97C147061CF9000F007C117D /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CLANG_ENABLE_MODULES = YES; 490 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 491 | DEVELOPMENT_TEAM = A97AY86M9N; 492 | ENABLE_BITCODE = NO; 493 | INFOPLIST_FILE = Runner/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = ( 495 | "$(inherited)", 496 | "@executable_path/Frameworks", 497 | ); 498 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMoviesApp; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 501 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 502 | SWIFT_VERSION = 5.0; 503 | VERSIONING_SYSTEM = "apple-generic"; 504 | }; 505 | name = Debug; 506 | }; 507 | 97C147071CF9000F007C117D /* Release */ = { 508 | isa = XCBuildConfiguration; 509 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 510 | buildSettings = { 511 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 512 | CLANG_ENABLE_MODULES = YES; 513 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 514 | DEVELOPMENT_TEAM = A97AY86M9N; 515 | ENABLE_BITCODE = NO; 516 | INFOPLIST_FILE = Runner/Info.plist; 517 | LD_RUNPATH_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "@executable_path/Frameworks", 520 | ); 521 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterMoviesApp; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 524 | SWIFT_VERSION = 5.0; 525 | VERSIONING_SYSTEM = "apple-generic"; 526 | }; 527 | name = Release; 528 | }; 529 | /* End XCBuildConfiguration section */ 530 | 531 | /* Begin XCConfigurationList section */ 532 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 97C147031CF9000F007C117D /* Debug */, 536 | 97C147041CF9000F007C117D /* Release */, 537 | 249021D3217E4FDB00AE95B9 /* Profile */, 538 | ); 539 | defaultConfigurationIsVisible = 0; 540 | defaultConfigurationName = Release; 541 | }; 542 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 543 | isa = XCConfigurationList; 544 | buildConfigurations = ( 545 | 97C147061CF9000F007C117D /* Debug */, 546 | 97C147071CF9000F007C117D /* Release */, 547 | 249021D4217E4FDB00AE95B9 /* Profile */, 548 | ); 549 | defaultConfigurationIsVisible = 0; 550 | defaultConfigurationName = Release; 551 | }; 552 | /* End XCConfigurationList section */ 553 | }; 554 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 555 | } 556 | -------------------------------------------------------------------------------- /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 | sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "61.0.0" 12 | analyzer: 13 | dependency: transitive 14 | description: 15 | name: analyzer 16 | sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "5.13.0" 20 | args: 21 | dependency: transitive 22 | description: 23 | name: args 24 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.4.2" 28 | async: 29 | dependency: transitive 30 | description: 31 | name: async 32 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.11.0" 36 | boolean_selector: 37 | dependency: transitive 38 | description: 39 | name: boolean_selector 40 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.1.1" 44 | build: 45 | dependency: transitive 46 | description: 47 | name: build 48 | sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "2.4.1" 52 | build_config: 53 | dependency: transitive 54 | description: 55 | name: build_config 56 | sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.1.1" 60 | build_daemon: 61 | dependency: transitive 62 | description: 63 | name: build_daemon 64 | sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "4.0.0" 68 | build_resolvers: 69 | dependency: transitive 70 | description: 71 | name: build_resolvers 72 | sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "2.2.1" 76 | build_runner: 77 | dependency: "direct dev" 78 | description: 79 | name: build_runner 80 | sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.4.6" 84 | build_runner_core: 85 | dependency: transitive 86 | description: 87 | name: build_runner_core 88 | sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "7.2.10" 92 | built_collection: 93 | dependency: transitive 94 | description: 95 | name: built_collection 96 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "5.1.1" 100 | built_value: 101 | dependency: transitive 102 | description: 103 | name: built_value 104 | sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "8.6.1" 108 | cached_network_image: 109 | dependency: "direct main" 110 | description: 111 | name: cached_network_image 112 | sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "3.2.3" 116 | cached_network_image_platform_interface: 117 | dependency: transitive 118 | description: 119 | name: cached_network_image_platform_interface 120 | sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "2.0.0" 124 | cached_network_image_web: 125 | dependency: transitive 126 | description: 127 | name: cached_network_image_web 128 | sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.0.2" 132 | characters: 133 | dependency: transitive 134 | description: 135 | name: characters 136 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "1.3.0" 140 | checked_yaml: 141 | dependency: transitive 142 | description: 143 | name: checked_yaml 144 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "2.0.3" 148 | clock: 149 | dependency: transitive 150 | description: 151 | name: clock 152 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.1.1" 156 | code_builder: 157 | dependency: transitive 158 | description: 159 | name: code_builder 160 | sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "4.5.0" 164 | collection: 165 | dependency: transitive 166 | description: 167 | name: collection 168 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.17.1" 172 | convert: 173 | dependency: transitive 174 | description: 175 | name: convert 176 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "3.1.1" 180 | coverage: 181 | dependency: transitive 182 | description: 183 | name: coverage 184 | sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "1.6.3" 188 | crypto: 189 | dependency: transitive 190 | description: 191 | name: crypto 192 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "3.0.3" 196 | dart_style: 197 | dependency: transitive 198 | description: 199 | name: dart_style 200 | sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "2.3.2" 204 | dio: 205 | dependency: "direct main" 206 | description: 207 | name: dio 208 | sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993" 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "5.3.0" 212 | fake_async: 213 | dependency: transitive 214 | description: 215 | name: fake_async 216 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "1.3.1" 220 | ffi: 221 | dependency: transitive 222 | description: 223 | name: ffi 224 | sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "2.0.2" 228 | file: 229 | dependency: transitive 230 | description: 231 | name: file 232 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "6.1.4" 236 | fixnum: 237 | dependency: transitive 238 | description: 239 | name: fixnum 240 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "1.1.0" 244 | flutter: 245 | dependency: "direct main" 246 | description: flutter 247 | source: sdk 248 | version: "0.0.0" 249 | flutter_blurhash: 250 | dependency: transitive 251 | description: 252 | name: flutter_blurhash 253 | sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "0.7.0" 257 | flutter_cache_manager: 258 | dependency: transitive 259 | description: 260 | name: flutter_cache_manager 261 | sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "3.3.1" 265 | flutter_localizations: 266 | dependency: "direct main" 267 | description: flutter 268 | source: sdk 269 | version: "0.0.0" 270 | flutter_test: 271 | dependency: "direct dev" 272 | description: flutter 273 | source: sdk 274 | version: "0.0.0" 275 | flutter_web_plugins: 276 | dependency: transitive 277 | description: flutter 278 | source: sdk 279 | version: "0.0.0" 280 | freezed: 281 | dependency: "direct dev" 282 | description: 283 | name: freezed 284 | sha256: "2df89855fe181baae3b6d714dc3c4317acf4fccd495a6f36e5e00f24144c6c3b" 285 | url: "https://pub.dev" 286 | source: hosted 287 | version: "2.4.1" 288 | freezed_annotation: 289 | dependency: "direct main" 290 | description: 291 | name: freezed_annotation 292 | sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d 293 | url: "https://pub.dev" 294 | source: hosted 295 | version: "2.4.1" 296 | frontend_server_client: 297 | dependency: transitive 298 | description: 299 | name: frontend_server_client 300 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" 301 | url: "https://pub.dev" 302 | source: hosted 303 | version: "3.2.0" 304 | glob: 305 | dependency: transitive 306 | description: 307 | name: glob 308 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 309 | url: "https://pub.dev" 310 | source: hosted 311 | version: "2.1.2" 312 | graphs: 313 | dependency: transitive 314 | description: 315 | name: graphs 316 | sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 317 | url: "https://pub.dev" 318 | source: hosted 319 | version: "2.3.1" 320 | http: 321 | dependency: transitive 322 | description: 323 | name: http 324 | sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "1.1.0" 328 | http_multi_server: 329 | dependency: transitive 330 | description: 331 | name: http_multi_server 332 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "3.2.1" 336 | http_parser: 337 | dependency: transitive 338 | description: 339 | name: http_parser 340 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "4.0.2" 344 | infinite_scroll_pagination: 345 | dependency: "direct main" 346 | description: 347 | name: infinite_scroll_pagination 348 | sha256: "9517328f4e373f08f57dbb11c5aac5b05554142024d6b60c903f3b73476d52db" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "3.2.0" 352 | intl: 353 | dependency: "direct main" 354 | description: 355 | name: intl 356 | sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "0.18.0" 360 | io: 361 | dependency: transitive 362 | description: 363 | name: io 364 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "1.0.4" 368 | js: 369 | dependency: transitive 370 | description: 371 | name: js 372 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "0.6.7" 376 | json_annotation: 377 | dependency: "direct main" 378 | description: 379 | name: json_annotation 380 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "4.8.1" 384 | json_serializable: 385 | dependency: "direct dev" 386 | description: 387 | name: json_serializable 388 | sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "6.7.1" 392 | lint: 393 | dependency: "direct dev" 394 | description: 395 | name: lint 396 | sha256: f4bd4dbaa39f4ae8836f2d1275f2f32bc68b3a8cce0a0735dd1f7a601f06682a 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "2.1.2" 400 | logger: 401 | dependency: "direct main" 402 | description: 403 | name: logger 404 | sha256: "66cb048220ca51cf9011da69fa581e4ee2bed4be6e82870d9e9baae75739da49" 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "2.0.1" 408 | logging: 409 | dependency: transitive 410 | description: 411 | name: logging 412 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "1.2.0" 416 | matcher: 417 | dependency: transitive 418 | description: 419 | name: matcher 420 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 421 | url: "https://pub.dev" 422 | source: hosted 423 | version: "0.12.15" 424 | material_color_utilities: 425 | dependency: transitive 426 | description: 427 | name: material_color_utilities 428 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 429 | url: "https://pub.dev" 430 | source: hosted 431 | version: "0.2.0" 432 | meta: 433 | dependency: transitive 434 | description: 435 | name: meta 436 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 437 | url: "https://pub.dev" 438 | source: hosted 439 | version: "1.9.1" 440 | mime: 441 | dependency: transitive 442 | description: 443 | name: mime 444 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e 445 | url: "https://pub.dev" 446 | source: hosted 447 | version: "1.0.4" 448 | mocktail: 449 | dependency: "direct dev" 450 | description: 451 | name: mocktail 452 | sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" 453 | url: "https://pub.dev" 454 | source: hosted 455 | version: "0.3.0" 456 | nested: 457 | dependency: transitive 458 | description: 459 | name: nested 460 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 461 | url: "https://pub.dev" 462 | source: hosted 463 | version: "1.0.0" 464 | node_preamble: 465 | dependency: transitive 466 | description: 467 | name: node_preamble 468 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" 469 | url: "https://pub.dev" 470 | source: hosted 471 | version: "2.0.2" 472 | octo_image: 473 | dependency: transitive 474 | description: 475 | name: octo_image 476 | sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" 477 | url: "https://pub.dev" 478 | source: hosted 479 | version: "1.0.2" 480 | package_config: 481 | dependency: transitive 482 | description: 483 | name: package_config 484 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 485 | url: "https://pub.dev" 486 | source: hosted 487 | version: "2.1.0" 488 | path: 489 | dependency: "direct main" 490 | description: 491 | name: path 492 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 493 | url: "https://pub.dev" 494 | source: hosted 495 | version: "1.8.3" 496 | path_provider: 497 | dependency: "direct main" 498 | description: 499 | name: path_provider 500 | sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" 501 | url: "https://pub.dev" 502 | source: hosted 503 | version: "2.0.15" 504 | path_provider_android: 505 | dependency: transitive 506 | description: 507 | name: path_provider_android 508 | sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" 509 | url: "https://pub.dev" 510 | source: hosted 511 | version: "2.0.27" 512 | path_provider_foundation: 513 | dependency: transitive 514 | description: 515 | name: path_provider_foundation 516 | sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" 517 | url: "https://pub.dev" 518 | source: hosted 519 | version: "2.2.4" 520 | path_provider_linux: 521 | dependency: transitive 522 | description: 523 | name: path_provider_linux 524 | sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 525 | url: "https://pub.dev" 526 | source: hosted 527 | version: "2.1.11" 528 | path_provider_platform_interface: 529 | dependency: transitive 530 | description: 531 | name: path_provider_platform_interface 532 | sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" 533 | url: "https://pub.dev" 534 | source: hosted 535 | version: "2.0.6" 536 | path_provider_windows: 537 | dependency: transitive 538 | description: 539 | name: path_provider_windows 540 | sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" 541 | url: "https://pub.dev" 542 | source: hosted 543 | version: "2.1.7" 544 | platform: 545 | dependency: transitive 546 | description: 547 | name: platform 548 | sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "3.1.0" 552 | plugin_platform_interface: 553 | dependency: transitive 554 | description: 555 | name: plugin_platform_interface 556 | sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "2.1.5" 560 | pool: 561 | dependency: transitive 562 | description: 563 | name: pool 564 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 565 | url: "https://pub.dev" 566 | source: hosted 567 | version: "1.5.1" 568 | provider: 569 | dependency: "direct main" 570 | description: 571 | name: provider 572 | sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f 573 | url: "https://pub.dev" 574 | source: hosted 575 | version: "6.0.5" 576 | pub_semver: 577 | dependency: transitive 578 | description: 579 | name: pub_semver 580 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 581 | url: "https://pub.dev" 582 | source: hosted 583 | version: "2.1.4" 584 | pubspec_parse: 585 | dependency: transitive 586 | description: 587 | name: pubspec_parse 588 | sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 589 | url: "https://pub.dev" 590 | source: hosted 591 | version: "1.2.3" 592 | rxdart: 593 | dependency: transitive 594 | description: 595 | name: rxdart 596 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 597 | url: "https://pub.dev" 598 | source: hosted 599 | version: "0.27.7" 600 | shared_preferences: 601 | dependency: "direct main" 602 | description: 603 | name: shared_preferences 604 | sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" 605 | url: "https://pub.dev" 606 | source: hosted 607 | version: "2.2.0" 608 | shared_preferences_android: 609 | dependency: transitive 610 | description: 611 | name: shared_preferences_android 612 | sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 613 | url: "https://pub.dev" 614 | source: hosted 615 | version: "2.2.0" 616 | shared_preferences_foundation: 617 | dependency: transitive 618 | description: 619 | name: shared_preferences_foundation 620 | sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 621 | url: "https://pub.dev" 622 | source: hosted 623 | version: "2.3.2" 624 | shared_preferences_linux: 625 | dependency: transitive 626 | description: 627 | name: shared_preferences_linux 628 | sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" 629 | url: "https://pub.dev" 630 | source: hosted 631 | version: "2.3.0" 632 | shared_preferences_platform_interface: 633 | dependency: transitive 634 | description: 635 | name: shared_preferences_platform_interface 636 | sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" 637 | url: "https://pub.dev" 638 | source: hosted 639 | version: "2.3.0" 640 | shared_preferences_web: 641 | dependency: transitive 642 | description: 643 | name: shared_preferences_web 644 | sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" 645 | url: "https://pub.dev" 646 | source: hosted 647 | version: "2.2.0" 648 | shared_preferences_windows: 649 | dependency: transitive 650 | description: 651 | name: shared_preferences_windows 652 | sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d 653 | url: "https://pub.dev" 654 | source: hosted 655 | version: "2.3.0" 656 | shelf: 657 | dependency: transitive 658 | description: 659 | name: shelf 660 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 661 | url: "https://pub.dev" 662 | source: hosted 663 | version: "1.4.1" 664 | shelf_packages_handler: 665 | dependency: transitive 666 | description: 667 | name: shelf_packages_handler 668 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" 669 | url: "https://pub.dev" 670 | source: hosted 671 | version: "3.0.2" 672 | shelf_static: 673 | dependency: transitive 674 | description: 675 | name: shelf_static 676 | sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e 677 | url: "https://pub.dev" 678 | source: hosted 679 | version: "1.1.2" 680 | shelf_web_socket: 681 | dependency: transitive 682 | description: 683 | name: shelf_web_socket 684 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" 685 | url: "https://pub.dev" 686 | source: hosted 687 | version: "1.0.4" 688 | sky_engine: 689 | dependency: transitive 690 | description: flutter 691 | source: sdk 692 | version: "0.0.99" 693 | sliver_tools: 694 | dependency: transitive 695 | description: 696 | name: sliver_tools 697 | sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 698 | url: "https://pub.dev" 699 | source: hosted 700 | version: "0.2.12" 701 | source_gen: 702 | dependency: transitive 703 | description: 704 | name: source_gen 705 | sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 706 | url: "https://pub.dev" 707 | source: hosted 708 | version: "1.4.0" 709 | source_helper: 710 | dependency: transitive 711 | description: 712 | name: source_helper 713 | sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" 714 | url: "https://pub.dev" 715 | source: hosted 716 | version: "1.3.4" 717 | source_map_stack_trace: 718 | dependency: transitive 719 | description: 720 | name: source_map_stack_trace 721 | sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" 722 | url: "https://pub.dev" 723 | source: hosted 724 | version: "2.1.1" 725 | source_maps: 726 | dependency: transitive 727 | description: 728 | name: source_maps 729 | sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" 730 | url: "https://pub.dev" 731 | source: hosted 732 | version: "0.10.12" 733 | source_span: 734 | dependency: transitive 735 | description: 736 | name: source_span 737 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 738 | url: "https://pub.dev" 739 | source: hosted 740 | version: "1.9.1" 741 | sqflite: 742 | dependency: "direct main" 743 | description: 744 | name: sqflite 745 | sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" 746 | url: "https://pub.dev" 747 | source: hosted 748 | version: "2.3.0" 749 | sqflite_common: 750 | dependency: transitive 751 | description: 752 | name: sqflite_common 753 | sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" 754 | url: "https://pub.dev" 755 | source: hosted 756 | version: "2.5.0" 757 | stack_trace: 758 | dependency: transitive 759 | description: 760 | name: stack_trace 761 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 762 | url: "https://pub.dev" 763 | source: hosted 764 | version: "1.11.0" 765 | stream_channel: 766 | dependency: transitive 767 | description: 768 | name: stream_channel 769 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 770 | url: "https://pub.dev" 771 | source: hosted 772 | version: "2.1.1" 773 | stream_transform: 774 | dependency: transitive 775 | description: 776 | name: stream_transform 777 | sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" 778 | url: "https://pub.dev" 779 | source: hosted 780 | version: "2.1.0" 781 | string_scanner: 782 | dependency: transitive 783 | description: 784 | name: string_scanner 785 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 786 | url: "https://pub.dev" 787 | source: hosted 788 | version: "1.2.0" 789 | synchronized: 790 | dependency: transitive 791 | description: 792 | name: synchronized 793 | sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" 794 | url: "https://pub.dev" 795 | source: hosted 796 | version: "3.1.0" 797 | term_glyph: 798 | dependency: transitive 799 | description: 800 | name: term_glyph 801 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 802 | url: "https://pub.dev" 803 | source: hosted 804 | version: "1.2.1" 805 | test: 806 | dependency: transitive 807 | description: 808 | name: test 809 | sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" 810 | url: "https://pub.dev" 811 | source: hosted 812 | version: "1.24.1" 813 | test_api: 814 | dependency: transitive 815 | description: 816 | name: test_api 817 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 818 | url: "https://pub.dev" 819 | source: hosted 820 | version: "0.5.1" 821 | test_core: 822 | dependency: transitive 823 | description: 824 | name: test_core 825 | sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" 826 | url: "https://pub.dev" 827 | source: hosted 828 | version: "0.5.1" 829 | timing: 830 | dependency: transitive 831 | description: 832 | name: timing 833 | sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" 834 | url: "https://pub.dev" 835 | source: hosted 836 | version: "1.0.1" 837 | typed_data: 838 | dependency: transitive 839 | description: 840 | name: typed_data 841 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 842 | url: "https://pub.dev" 843 | source: hosted 844 | version: "1.3.2" 845 | uuid: 846 | dependency: transitive 847 | description: 848 | name: uuid 849 | sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" 850 | url: "https://pub.dev" 851 | source: hosted 852 | version: "3.0.7" 853 | vector_math: 854 | dependency: transitive 855 | description: 856 | name: vector_math 857 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 858 | url: "https://pub.dev" 859 | source: hosted 860 | version: "2.1.4" 861 | vm_service: 862 | dependency: transitive 863 | description: 864 | name: vm_service 865 | sha256: ada49637c27973c183dad90beb6bd781eea4c9f5f955d35da172de0af7bd3440 866 | url: "https://pub.dev" 867 | source: hosted 868 | version: "11.8.0" 869 | watcher: 870 | dependency: transitive 871 | description: 872 | name: watcher 873 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 874 | url: "https://pub.dev" 875 | source: hosted 876 | version: "1.1.0" 877 | web_socket_channel: 878 | dependency: transitive 879 | description: 880 | name: web_socket_channel 881 | sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b 882 | url: "https://pub.dev" 883 | source: hosted 884 | version: "2.4.0" 885 | webkit_inspection_protocol: 886 | dependency: transitive 887 | description: 888 | name: webkit_inspection_protocol 889 | sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" 890 | url: "https://pub.dev" 891 | source: hosted 892 | version: "1.2.0" 893 | win32: 894 | dependency: transitive 895 | description: 896 | name: win32 897 | sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 898 | url: "https://pub.dev" 899 | source: hosted 900 | version: "5.0.6" 901 | xdg_directories: 902 | dependency: transitive 903 | description: 904 | name: xdg_directories 905 | sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff 906 | url: "https://pub.dev" 907 | source: hosted 908 | version: "1.0.1" 909 | yaml: 910 | dependency: transitive 911 | description: 912 | name: yaml 913 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 914 | url: "https://pub.dev" 915 | source: hosted 916 | version: "3.1.2" 917 | sdks: 918 | dart: ">=3.0.1 <4.0.0" 919 | flutter: ">=3.3.0" 920 | --------------------------------------------------------------------------------