├── 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