├── android ├── settings_aar.gradle ├── 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 │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── movingPictures │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── .settings │ └── org.eclipse.buildship.core.prefs ├── settings.gradle ├── build.gradle └── .project ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist └── .gitignore ├── .vscode └── settings.json ├── screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── MP-Banner.png └── moving_pictures_logo_man_boxy-removebg-preview (1).png ├── assets ├── images │ ├── tempo.jpg │ ├── johnwick.png │ ├── the_queens_gambit.png │ └── the_queens_gambit_movie_poster.jpg ├── logos │ ├── tmdb-logo.png │ ├── moving_pictures_logo_man.png │ ├── moving_pictures_logo_red.png │ ├── moving_pictures_logo_man_boxy.png │ └── moving_pictures_logo_man_boxy_no_background.png └── icons │ ├── regularIcons │ ├── google_icon.png │ ├── search.svg │ ├── cancel.svg │ ├── heart_filled.svg │ ├── info.svg │ ├── heart.svg │ ├── home.svg │ ├── play.svg │ ├── friends.svg │ ├── shield.svg │ ├── share.svg │ ├── bell.svg │ └── github.svg │ └── numberIcons │ └── 7.svg ├── fonts ├── Roboto │ ├── Roboto-Black.ttf │ ├── Roboto-Bold.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-Thin.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-BoldItalic.ttf │ ├── Roboto-ThinItalic.ttf │ ├── Roboto-BlackItalic.ttf │ ├── Roboto-LightItalic.ttf │ └── Roboto-MediumItalic.ttf └── Montez │ └── Montez-Regular.ttf ├── analysis_options.yaml ├── lib ├── application │ ├── home │ │ ├── casts │ │ │ ├── casts_event.dart │ │ │ ├── casts_state.dart │ │ │ └── casts_bloc.dart │ │ ├── movies │ │ │ ├── casts │ │ │ │ ├── casts_event.dart │ │ │ │ ├── casts_state.dart │ │ │ │ └── casts_bloc.dart │ │ │ ├── genres │ │ │ │ ├── genres_event.dart │ │ │ │ ├── genres_state.dart │ │ │ │ └── genres_bloc.dart │ │ │ ├── movies │ │ │ │ ├── movies_state.dart │ │ │ │ ├── movies_event.dart │ │ │ │ └── movies_bloc.dart │ │ │ └── favorite_movies │ │ │ │ ├── favoritemovies_event.dart │ │ │ │ ├── favoritemovies_state.dart │ │ │ │ └── favoritemovies_bloc.dart │ │ └── series │ │ │ ├── casts │ │ │ ├── casts_event.dart │ │ │ ├── casts_state.dart │ │ │ └── casts_bloc.dart │ │ │ ├── genres │ │ │ ├── genres_event.dart │ │ │ ├── genres_state.dart │ │ │ └── genres_bloc.dart │ │ │ ├── series │ │ │ ├── series_state.dart │ │ │ ├── series_event.dart │ │ │ └── series_bloc.dart │ │ │ └── favorite_series │ │ │ ├── favoriteseries_event.dart │ │ │ ├── favoriteseries_state.dart │ │ │ └── favoriteseries_bloc.dart │ ├── auth │ │ ├── sign_in │ │ │ ├── sign_in_event.dart │ │ │ ├── sign_in_state.dart │ │ │ └── sign_in_bloc.dart │ │ ├── auth_state.dart │ │ ├── auth_event.dart │ │ ├── user_profile │ │ │ ├── user_profile_event.dart │ │ │ ├── user_profile_state.dart │ │ │ └── user_profile_bloc.dart │ │ └── auth_bloc.dart │ ├── search │ │ ├── search_event.dart │ │ ├── search_state.dart │ │ └── search_bloc.dart │ └── People │ │ ├── people_state.dart │ │ └── people_event.dart ├── domain │ ├── home │ │ ├── shared_classes │ │ │ ├── cast │ │ │ │ ├── cast_interface.dart │ │ │ │ ├── cast_failure.dart │ │ │ │ ├── cast.dart │ │ │ │ └── cast.g.dart │ │ │ ├── genres │ │ │ │ ├── genre_failure.dart │ │ │ │ ├── genre.dart │ │ │ │ └── genre.g.dart │ │ │ └── videos.dart │ │ ├── series │ │ │ ├── favorite_series_interface.dart │ │ │ ├── serie │ │ │ │ ├── serie_failure.dart │ │ │ │ ├── content_ratings.dart │ │ │ │ ├── serie.g.dart │ │ │ │ └── serie.dart │ │ │ ├── favorite_serie │ │ │ │ └── favorite_serie.dart │ │ │ ├── serie_sub │ │ │ │ └── serie_sub.dart │ │ │ └── series_interface.dart │ │ └── movies │ │ │ ├── favorite_movies_interface.dart │ │ │ ├── movie │ │ │ ├── movies_failure.dart │ │ │ ├── release_dates.dart │ │ │ ├── movie.g.dart │ │ │ └── movie.dart │ │ │ ├── favorite_movies │ │ │ └── favorite_movies.dart │ │ │ ├── movie_sub │ │ │ └── movie_sub.dart │ │ │ └── movies_interface.dart │ ├── search │ │ ├── search_interface.dart │ │ └── search.dart │ ├── core │ │ ├── failures.dart │ │ ├── errors.dart │ │ └── value_objects.dart │ ├── auth │ │ ├── app_user_failure.dart │ │ ├── auth_failure.dart │ │ ├── auth_repository_interface.dart │ │ ├── app_user.g.dart │ │ └── app_user.dart │ └── people │ │ ├── people_failure.dart │ │ ├── people_interface.dart │ │ └── people.dart ├── injection.dart ├── presentation │ ├── core │ │ ├── app_colors.dart │ │ ├── component_widgets │ │ │ ├── flushbar_method.dart │ │ │ ├── cancel_button_widget.dart │ │ │ ├── poster_image_widget.dart │ │ │ ├── age_restriction_widget.dart │ │ │ ├── saving_in_progress_widget.dart │ │ │ ├── movie_loading_wigdet.dart │ │ │ └── primary_button_widget.dart │ │ ├── constants │ │ │ ├── language_constants.dart │ │ │ └── constants.dart │ │ ├── app_localizations.dart │ │ └── app_widget.dart │ ├── signin │ │ └── sign_in_screen.dart │ ├── search │ │ ├── search_screen.dart │ │ └── widgets │ │ │ ├── search_bar.dart │ │ │ ├── search_movies.dart │ │ │ ├── search_series.dart │ │ │ └── search_cast.dart │ ├── splash │ │ └── splash_screen.dart │ ├── routes │ │ └── router.dart │ ├── home │ │ ├── movies │ │ │ ├── movie_info │ │ │ │ ├── widgets │ │ │ │ │ ├── genres_list.dart │ │ │ │ │ └── sub_data.dart │ │ │ │ └── movie_info.dart │ │ │ ├── movies_tab_screen.dart │ │ │ └── widgets │ │ │ │ └── little_favorite_sub_data_icon.dart │ │ ├── series │ │ │ ├── serie_info │ │ │ │ ├── widgets │ │ │ │ │ ├── genres_list.dart │ │ │ │ │ └── sub_data.dart │ │ │ │ └── serie_info.dart │ │ │ ├── series_tab_screen.dart │ │ │ └── widgets │ │ │ │ └── little_favorite_sub_data_icon.dart │ │ └── home.dart │ ├── profile │ │ └── widgets │ │ │ ├── github_block.dart │ │ │ ├── tmdb_block.dart │ │ │ └── profile_info_block_widget.dart │ ├── people │ │ └── widgets │ │ │ └── people_search_bar.dart │ ├── main_layout_appbar_navbar │ │ ├── main_bottom_navigation_bar_widget.dart │ │ └── main_body_layout.dart │ └── favorites │ │ └── favorites.dart ├── infrastructure │ ├── auth │ │ └── firebase_user_mapper.dart │ ├── core │ │ ├── firebase_injectable_module.dart │ │ └── firestore_helper.dart │ ├── home │ │ ├── casts │ │ │ └── casts_repository.dart │ │ ├── series │ │ │ └── favorite_series_repository.dart │ │ └── movies │ │ │ └── favorite_movies_repository.dart │ └── search │ │ └── search_repository.dart └── main.dart ├── .metadata ├── .gitignore ├── LICENSE ├── lang ├── en.json └── pt.json └── pubspec.yaml /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/6.png -------------------------------------------------------------------------------- /screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/7.png -------------------------------------------------------------------------------- /assets/images/tempo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/images/tempo.jpg -------------------------------------------------------------------------------- /assets/images/johnwick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/images/johnwick.png -------------------------------------------------------------------------------- /assets/logos/tmdb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/logos/tmdb-logo.png -------------------------------------------------------------------------------- /screenshots/MP-Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/MP-Banner.png -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /fonts/Montez/Montez-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Montez/Montez-Regular.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /assets/images/the_queens_gambit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/images/the_queens_gambit.png -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml 2 | 3 | analyzer: 4 | 5 | errors: 6 | # missing_return: error 7 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/google_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/icons/regularIcons/google_icon.png -------------------------------------------------------------------------------- /assets/logos/moving_pictures_logo_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/logos/moving_pictures_logo_man.png -------------------------------------------------------------------------------- /assets/logos/moving_pictures_logo_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/logos/moving_pictures_logo_red.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /assets/logos/moving_pictures_logo_man_boxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/logos/moving_pictures_logo_man_boxy.png -------------------------------------------------------------------------------- /assets/images/the_queens_gambit_movie_poster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/images/the_queens_gambit_movie_poster.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /assets/logos/moving_pictures_logo_man_boxy_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/assets/logos/moving_pictures_logo_man_boxy_no_background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /screenshots/moving_pictures_logo_man_boxy-removebg-preview (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/HEAD/screenshots/moving_pictures_logo_man_boxy-removebg-preview (1).png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/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/thenifemi/movingPictures/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thenifemi/movingPictures/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/thenifemi/movingPictures/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/application/home/casts/casts_event.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsEvent with _$CastsEvent { 5 | const factory CastsEvent.getCastCalled(int castId) = _GetCastCalled; 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/movingPictures/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nifemi.movingPictures 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/application/home/movies/casts/casts_event.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsEvent with _$CastsEvent { 5 | const factory CastsEvent.getCastCalled(int movieId) = _GetCastCalled; 6 | } 7 | -------------------------------------------------------------------------------- /lib/application/home/movies/genres/genres_event.dart: -------------------------------------------------------------------------------- 1 | part of 'genres_bloc.dart'; 2 | 3 | @freezed 4 | abstract class GenresEvent with _$GenresEvent { 5 | const factory GenresEvent.getGenresCalled() = _GetGenresCalled; 6 | } 7 | -------------------------------------------------------------------------------- /lib/application/home/series/casts/casts_event.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsEvent with _$CastsEvent { 5 | const factory CastsEvent.getCastCalled(int serieId) = _GetCastCalled; 6 | } 7 | -------------------------------------------------------------------------------- /lib/application/home/series/genres/genres_event.dart: -------------------------------------------------------------------------------- 1 | part of 'genres_bloc.dart'; 2 | 3 | @freezed 4 | abstract class GenresEvent with _$GenresEvent { 5 | const factory GenresEvent.getGenresCalled() = _GetGenresCalled; 6 | } 7 | -------------------------------------------------------------------------------- /lib/application/auth/sign_in/sign_in_event.dart: -------------------------------------------------------------------------------- 1 | part of 'sign_in_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SignInEvent with _$SignInEvent { 5 | const factory SignInEvent.signInwithGooglePressed() = SignInWithGooglePressed; 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/cast/cast_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'cast.dart'; 4 | import 'cast_failure.dart'; 5 | 6 | abstract class CastInterface { 7 | Future> getPerson(int castId); 8 | } 9 | -------------------------------------------------------------------------------- /lib/application/search/search_event.dart: -------------------------------------------------------------------------------- 1 | part of 'search_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SearchEvent with _$SearchEvent { 5 | const factory SearchEvent.trendingCalled() = _TrendingCalled; 6 | const factory SearchEvent.queryCalled(String query) = _QueryCalled; 7 | } 8 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/cast/cast_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'cast_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class CastFailure with _$CastFailure { 7 | const factory CastFailure.unexpected() = _Unexpected; 8 | } 9 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/cancel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/application/auth/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | @freezed 4 | abstract class AuthState with _$AuthState { 5 | const factory AuthState.initial() = _Initial; 6 | const factory AuthState.authenticated() = _Authenticated; 7 | const factory AuthState.unAuthenticated() = _UnAuthenticated; 8 | } 9 | -------------------------------------------------------------------------------- /lib/injection.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | import 'injection.config.dart'; 5 | 6 | final GetIt getIt = GetIt.instance; 7 | 8 | @injectableInit 9 | void configureInjection(String env) { 10 | $initGetIt(getIt, environment: env); 11 | } 12 | -------------------------------------------------------------------------------- /lib/presentation/core/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color black = Color(0xFF000000); 5 | static const Color red = Color(0xFFE50914); 6 | static const Color gray = Color(0xFF343434); 7 | static const Color white = Color(0xFFFFFFFF); 8 | } 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/heart_filled.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/application/auth/auth_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_bloc.dart'; 2 | 3 | @freezed 4 | abstract class AuthEvent with _$AuthEvent { 5 | const factory AuthEvent.authCheckRequested() = _AuthCheckRequested; 6 | const factory AuthEvent.signedOut() = _SignedOut; 7 | const factory AuthEvent.storeGoogleUser() = _StoreGoogleUser; 8 | } 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/domain/search/search_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import '../home/movies/movie/movies_failure.dart'; 4 | import 'search.dart'; 5 | 6 | abstract class SearchInterface { 7 | Future>> getTrending(); 8 | Future>> getSearchQuery(String query); 9 | } 10 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/domain/core/failures.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'failures.freezed.dart'; 3 | 4 | @freezed 5 | abstract class ValueFailure with _$ValueFailure { 6 | //Auth failures 7 | const factory ValueFailure.accountAlreadyExists({ 8 | @required T failedValue, 9 | }) = AccountAlreadyExists; 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/auth/app_user_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'app_user_failure.freezed.dart'; 3 | 4 | @freezed 5 | abstract class AppUserFailure with _$AppUserFailure { 6 | const factory AppUserFailure.unexpected() = _Unexpected; 7 | const factory AppUserFailure.insufficientPermissions() = 8 | _InsufficientPermissions; 9 | } 10 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/genres/genre_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'genre_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class GenreFailure with _$GenreFailure { 7 | const factory GenreFailure.unexpected() = _Unexpected; 8 | const factory GenreFailure.noInternetConnection() = _NoInternetConnection; 9 | } 10 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/application/auth/user_profile/user_profile_event.dart: -------------------------------------------------------------------------------- 1 | part of 'user_profile_bloc.dart'; 2 | 3 | @freezed 4 | abstract class UserProfileEvent with _$UserProfileEvent { 5 | const factory UserProfileEvent.watchProfileStarted() = _WatchProfileStarted; 6 | const factory UserProfileEvent.profileRecieved( 7 | Either failureOrProfile, 8 | ) = _ProfileRecieved; 9 | } 10 | -------------------------------------------------------------------------------- /lib/domain/auth/auth_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'auth_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class AuthFailure with _$AuthFailure { 7 | const factory AuthFailure.cancelledByUser() = CancelledByUser; 8 | const factory AuthFailure.serverError() = ServerError; 9 | const factory AuthFailure.unexpected() = Unexpected; 10 | } 11 | -------------------------------------------------------------------------------- /lib/infrastructure/auth/firebase_user_mapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart' as firebase; 2 | 3 | import '../../domain/auth/app_user.dart'; 4 | 5 | extension FirebaseUserDomainX on firebase.User { 6 | AppUser toDomain() { 7 | return AppUser( 8 | id: uid, 9 | name: displayName, 10 | photoURL: photoURL, 11 | email: email, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/application/home/casts/casts_state.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsState with _$CastsState { 5 | const factory CastsState.initial() = _Initial; 6 | const factory CastsState.loading() = _Loading; 7 | const factory CastsState.loadSuccess(Cast cast) = _LoadSuccess; 8 | 9 | const factory CastsState.loadFailure(CastFailure castFailure) = _LoadFailure; 10 | } 11 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/application/home/movies/casts/casts_state.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsState with _$CastsState { 5 | const factory CastsState.initial() = _Initial; 6 | const factory CastsState.loading() = _Loading; 7 | const factory CastsState.loadSuccess(List casts) = _LoadSuccess; 8 | const factory CastsState.loadFailure(CastFailure castFailure) = _LoadFailure; 9 | } 10 | -------------------------------------------------------------------------------- /lib/application/home/series/casts/casts_state.dart: -------------------------------------------------------------------------------- 1 | part of 'casts_bloc.dart'; 2 | 3 | @freezed 4 | abstract class CastsState with _$CastsState { 5 | const factory CastsState.initial() = _Initial; 6 | const factory CastsState.loading() = _Loading; 7 | const factory CastsState.loadSuccess(List casts) = _LoadSuccess; 8 | const factory CastsState.loadFailure(CastFailure castFailure) = _LoadFailure; 9 | } 10 | -------------------------------------------------------------------------------- /lib/application/home/movies/genres/genres_state.dart: -------------------------------------------------------------------------------- 1 | part of 'genres_bloc.dart'; 2 | 3 | @freezed 4 | abstract class GenresState with _$GenresState { 5 | const factory GenresState.initial() = _Initial; 6 | const factory GenresState.loading() = _Loading; 7 | const factory GenresState.loadSuccess(List genres) = _LoadSuccess; 8 | const factory GenresState.loadFailure(GenreFailure genreFailure) = 9 | _LoadFailure; 10 | } 11 | -------------------------------------------------------------------------------- /lib/application/home/series/genres/genres_state.dart: -------------------------------------------------------------------------------- 1 | part of 'genres_bloc.dart'; 2 | 3 | @freezed 4 | abstract class GenresState with _$GenresState { 5 | const factory GenresState.initial() = _Initial; 6 | const factory GenresState.loading() = _Loading; 7 | const factory GenresState.loadSuccess(List genres) = _LoadSuccess; 8 | const factory GenresState.loadFailure(GenreFailure genreFailure) = 9 | _LoadFailure; 10 | } 11 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=C\:/Program Files/Java/jdk-14.0.2 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:injectable/injectable.dart'; 4 | 5 | import 'injection.dart'; 6 | import 'presentation/core/app_widget.dart'; 7 | 8 | Future main() async { 9 | WidgetsFlutterBinding.ensureInitialized(); 10 | 11 | await Firebase.initializeApp(); 12 | 13 | configureInjection(Environment.prod); 14 | 15 | runApp(App()); 16 | } 17 | -------------------------------------------------------------------------------- /lib/domain/home/series/favorite_series_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'favorite_serie/favorite_serie.dart'; 4 | import 'serie/serie_failure.dart'; 5 | 6 | abstract class FavoriteSeriesInterface { 7 | Stream>> watchSerieFavorites(); 8 | Future> createFavoriteSerie(int serieId); 9 | Future> deleteFavoriteSerie(int serieId); 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/home/movies/favorite_movies_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'favorite_movies/favorite_movies.dart'; 4 | import 'movie/movies_failure.dart'; 5 | 6 | abstract class FavoriteMoviesInterface { 7 | Stream>> watchMovieFavorites(); 8 | Future> createFavoriteMovie(int movieId); 9 | Future> deleteFavoriteMovie(int movieId); 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/search/search.dart: -------------------------------------------------------------------------------- 1 | class Search { 2 | final int id; 3 | final String mediaType; 4 | 5 | Search({this.id, this.mediaType}); 6 | 7 | factory Search.fromJson(Map json) => Search( 8 | id: json['id'] as int ?? -0, 9 | mediaType: json['media_type'] as String, 10 | ); 11 | 12 | factory Search.toDomain(Search search) => Search( 13 | id: search.id, 14 | mediaType: search.mediaType, 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/friends.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/application/auth/user_profile/user_profile_state.dart: -------------------------------------------------------------------------------- 1 | part of 'user_profile_bloc.dart'; 2 | 3 | @freezed 4 | abstract class UserProfileState with _$UserProfileState { 5 | const factory UserProfileState.initial() = _Initial; 6 | const factory UserProfileState.loadingProgress() = _LoadingProgress; 7 | const factory UserProfileState.loadSuccess(AppUser appUser) = _LoadSuccess; 8 | const factory UserProfileState.loadFailure(AppUserFailure appUserFailure) = 9 | _LoadFailure; 10 | } 11 | -------------------------------------------------------------------------------- /lib/domain/core/errors.dart: -------------------------------------------------------------------------------- 1 | import 'failures.dart'; 2 | 3 | class NotAuthenticatedError extends Error {} 4 | 5 | class UnexpectedValueError extends Error { 6 | final ValueFailure valueFailure; 7 | 8 | UnexpectedValueError(this.valueFailure); 9 | 10 | @override 11 | String toString() { 12 | const explanation = 13 | 'Encountered a ValueFailure at an unrecoverable point. Terminating!'; 14 | return Error.safeToString('$explanation Failure was: $valueFailure'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/flushbar_method.dart: -------------------------------------------------------------------------------- 1 | import 'package:flushbar/flushbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Future showFlushbar({ 5 | @required BuildContext context, 6 | @required String message, 7 | }) { 8 | return Flushbar( 9 | message: message, 10 | flushbarStyle: FlushbarStyle.FLOATING, 11 | duration: const Duration(seconds: 3), 12 | margin: const EdgeInsets.all(15), 13 | borderRadius: 8, 14 | ).show(context); 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /lib/domain/people/people_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'people_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class PeopleFailure with _$PeopleFailure { 7 | const factory PeopleFailure.unexpected() = _Unexpected; 8 | const factory PeopleFailure.noInternetConnection() = _NoInternetConnection; 9 | const factory PeopleFailure.unableToAdd() = _UnableToAdd; 10 | const factory PeopleFailure.insufficientPermissions() = 11 | _InsufficientPermissions; 12 | } 13 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movie/movies_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'movies_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class MovieFailure with _$MovieFailure { 7 | const factory MovieFailure.unexpected() = _Unexpected; 8 | const factory MovieFailure.noInternetConnection() = _NoInternetConnection; 9 | const factory MovieFailure.unableToAdd() = _UnableToAdd; 10 | const factory MovieFailure.insufficientPermissions() = 11 | _InsufficientPermissions; 12 | } 13 | -------------------------------------------------------------------------------- /lib/domain/home/series/serie/serie_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'serie_failure.freezed.dart'; 4 | 5 | @freezed 6 | abstract class SerieFailure with _$SerieFailure { 7 | const factory SerieFailure.unexpected() = _Unexpected; 8 | const factory SerieFailure.noInternetConnection() = _NoInternetConnection; 9 | const factory SerieFailure.unableToAdd() = _UnableToAdd; 10 | const factory SerieFailure.insufficientPermissions() = 11 | _InsufficientPermissions; 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/application/home/movies/movies/movies_state.dart: -------------------------------------------------------------------------------- 1 | part of 'movies_bloc.dart'; 2 | 3 | @freezed 4 | abstract class MoviesState with _$MoviesState { 5 | const factory MoviesState.initial() = _Initial; 6 | const factory MoviesState.loading() = _Loading; 7 | const factory MoviesState.loadSuccess(List movies) = _LoadSuccess; 8 | const factory MoviesState.loadSuccessforMovie(Movie movie) = 9 | _LoadSuccessforBannerMovie; 10 | const factory MoviesState.loadFailure(MovieFailure movieFailure) = 11 | _LoadFailure; 12 | } 13 | -------------------------------------------------------------------------------- /lib/application/home/series/series/series_state.dart: -------------------------------------------------------------------------------- 1 | part of 'series_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SeriesState with _$SeriesState { 5 | const factory SeriesState.initial() = _Initial; 6 | const factory SeriesState.loading() = _Loading; 7 | const factory SeriesState.loadSuccess(List series) = _LoadSuccess; 8 | const factory SeriesState.loadSuccessforSerie(Serie serie) = 9 | _LoadSuccessforBannerMovie; 10 | const factory SeriesState.loadFailure(SerieFailure serieFailure) = 11 | _LoadFailure; 12 | } 13 | -------------------------------------------------------------------------------- /lib/domain/auth/auth_repository_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'app_user.dart'; 4 | import 'app_user_failure.dart'; 5 | import 'auth_failure.dart'; 6 | 7 | abstract class AuthInterface { 8 | // [Unit] can be read as void. 9 | Future> signInWithGoogle(); 10 | 11 | Future> getSignedInUser(); 12 | 13 | Future> storeGoogleUser(); 14 | 15 | Future signOut(); 16 | 17 | Stream> watchUserProfile(); 18 | } 19 | -------------------------------------------------------------------------------- /lib/application/auth/sign_in/sign_in_state.dart: -------------------------------------------------------------------------------- 1 | part of 'sign_in_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SignInState with _$SignInState { 5 | const factory SignInState({ 6 | @required bool showErrorMessages, 7 | @required bool isSubmitting, 8 | @required Option> authFailureOrSuccessOption, 9 | }) = _SignInState; 10 | 11 | factory SignInState.initial() => SignInState( 12 | showErrorMessages: false, 13 | isSubmitting: false, 14 | authFailureOrSuccessOption: none(), 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/shield.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/genres/genre.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'genre.freezed.dart'; 4 | part 'genre.g.dart'; 5 | 6 | @freezed 7 | abstract class Genre with _$Genre { 8 | const factory Genre({ 9 | @required int id, 10 | @required String name, 11 | }) = _Genre; 12 | const Genre._(); 13 | 14 | Genre toDomain() { 15 | return Genre( 16 | id: id, 17 | name: name, 18 | ); 19 | } 20 | 21 | factory Genre.fromJson(Map json) => _$GenreFromJson(json); 22 | } 23 | -------------------------------------------------------------------------------- /lib/application/search/search_state.dart: -------------------------------------------------------------------------------- 1 | part of 'search_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SearchState with _$SearchState { 5 | const factory SearchState.initial() = _Initial; 6 | const factory SearchState.loading() = _Loading; 7 | const factory SearchState.loadSuccess(List moviesOrSeries) = 8 | _LoadSuccess; 9 | const factory SearchState.loadSuccessforQuery( 10 | List moviesOrSeriesorPerson) = _LoadSuccessforQuery; 11 | const factory SearchState.loadFailure(MovieFailure movieFailure) = 12 | _LoadFailure; 13 | } 14 | -------------------------------------------------------------------------------- /lib/domain/people/people_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'people.dart'; 4 | import 'people_failure.dart'; 5 | 6 | abstract class PeopleInterface { 7 | Stream>> watchPeople(); 8 | Stream>> watchFullPeople(String email); 9 | Future> followPerson(String personEmail); 10 | Future> unFollowPerson(String personEmail); 11 | Future> getPersonSearchQuery( 12 | String personEmail); 13 | } 14 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/infrastructure/core/firebase_injectable_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:google_sign_in/google_sign_in.dart'; 4 | import 'package:injectable/injectable.dart'; 5 | 6 | @module 7 | abstract class FirebaseinjectableModule { 8 | @lazySingleton 9 | GoogleSignIn get googleSignIn => GoogleSignIn(); 10 | @lazySingleton 11 | FirebaseAuth get firebaseAuth => FirebaseAuth.instance; 12 | @lazySingleton 13 | FirebaseFirestore get firestore => FirebaseFirestore.instance; 14 | } 15 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/cast/cast.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'cast.freezed.dart'; 4 | part 'cast.g.dart'; 5 | 6 | @freezed 7 | abstract class Cast with _$Cast { 8 | const factory Cast({ 9 | @required int id, 10 | @required String name, 11 | @required String profilePath, 12 | }) = _Cast; 13 | 14 | const Cast._(); 15 | 16 | factory Cast.fromJson(Map json) => Cast( 17 | id: json['id'] as int ?? 0, 18 | name: json['name'] as String ?? "", 19 | profilePath: json['profile_path'] as String ?? ""); 20 | } 21 | -------------------------------------------------------------------------------- /lib/application/home/movies/movies/movies_event.dart: -------------------------------------------------------------------------------- 1 | part of 'movies_bloc.dart'; 2 | 3 | @freezed 4 | abstract class MoviesEvent with _$MoviesEvent { 5 | const factory MoviesEvent.movieTypeCalled(String movieListType) = 6 | _MovieTypeCalled; 7 | const factory MoviesEvent.movieByGenreCalled(int movieGenreId) = 8 | _MovieByGenreCalled; 9 | const factory MoviesEvent.similarMoviesCalled(int movieId) = 10 | _SimilarMoviesCalled; 11 | const factory MoviesEvent.movieCalled(int movieId) = _MovieCalled; 12 | const factory MoviesEvent.movieByCastIdCalled(int castId) = 13 | _MovieByCastIdCalled; 14 | } 15 | -------------------------------------------------------------------------------- /lib/application/home/series/series/series_event.dart: -------------------------------------------------------------------------------- 1 | part of 'series_bloc.dart'; 2 | 3 | @freezed 4 | abstract class SeriesEvent with _$SeriesEvent { 5 | const factory SeriesEvent.serieTypeCalled(String serieListType) = 6 | _SerieTypeCalled; 7 | const factory SeriesEvent.serieByGenreCalled(int serieGenreId) = 8 | _SerieByGenreCalled; 9 | const factory SeriesEvent.similarSeriesCalled(int serieId) = 10 | _SimilarSeriesCalled; 11 | const factory SeriesEvent.serieCalled(int serieId) = _SerieCalled; 12 | const factory SeriesEvent.serieByCastIdCalled(int castId) = 13 | _SerieByCastIdCalled; 14 | } 15 | -------------------------------------------------------------------------------- /lib/application/home/movies/favorite_movies/favoritemovies_event.dart: -------------------------------------------------------------------------------- 1 | part of 'favoritemovies_bloc.dart'; 2 | 3 | @freezed 4 | abstract class FavoritemoviesEvent with _$FavoritemoviesEvent { 5 | const factory FavoritemoviesEvent.favoriteCreated(int movieId) = 6 | _FavoriteCreated; 7 | const factory FavoritemoviesEvent.favoriteDeleted(int movieId) = 8 | _FavoriteDeleted; 9 | const factory FavoritemoviesEvent.watchFavorites() = _WatchFavorites; 10 | const factory FavoritemoviesEvent.favoritesRecieved( 11 | Either> failureOrMovies) = 12 | _FavoritesRecieved; 13 | } 14 | -------------------------------------------------------------------------------- /lib/application/home/series/favorite_series/favoriteseries_event.dart: -------------------------------------------------------------------------------- 1 | part of 'favoriteseries_bloc.dart'; 2 | 3 | @freezed 4 | abstract class FavoriteseriesEvent with _$FavoriteseriesEvent { 5 | const factory FavoriteseriesEvent.favoriteCreated(int serieId) = 6 | _FavoriteCreated; 7 | const factory FavoriteseriesEvent.favoriteDeleted(int serieId) = 8 | _FavoriteDeleted; 9 | const factory FavoriteseriesEvent.watchFavorites() = _WatchFavorites; 10 | const factory FavoriteseriesEvent.favoritesRecieved( 11 | Either> failureOrSeries) = 12 | _FavoritesRecieved; 13 | } 14 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/genres/genre.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'genre.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Genre _$_$_GenreFromJson(Map json) { 10 | return _$_Genre( 11 | id: json['id'] as int, 12 | name: json['name'] as String, 13 | ); 14 | } 15 | 16 | Map _$_$_GenreToJson(_$_Genre instance) => { 17 | 'id': instance.id, 18 | 'name': instance.name, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/application/home/movies/favorite_movies/favoritemovies_state.dart: -------------------------------------------------------------------------------- 1 | part of 'favoritemovies_bloc.dart'; 2 | 3 | @freezed 4 | abstract class FavoritemoviesState with _$FavoritemoviesState { 5 | const factory FavoritemoviesState.initial() = _Initial; 6 | const factory FavoritemoviesState.loading() = _Loading; 7 | const factory FavoritemoviesState.failure(MovieFailure movieFailure) = 8 | _Failure; 9 | const factory FavoritemoviesState.createSuccess() = _CreateSuccess; 10 | const factory FavoritemoviesState.deleteSuccess() = _DeleteSuccess; 11 | const factory FavoritemoviesState.watchSuccess( 12 | List favoriteMovies) = _WatchSuccess; 13 | } 14 | -------------------------------------------------------------------------------- /lib/application/home/series/favorite_series/favoriteseries_state.dart: -------------------------------------------------------------------------------- 1 | part of 'favoriteseries_bloc.dart'; 2 | 3 | @freezed 4 | abstract class FavoriteseriesState with _$FavoriteseriesState { 5 | const factory FavoriteseriesState.initial() = _Initial; 6 | const factory FavoriteseriesState.loading() = _Loading; 7 | const factory FavoriteseriesState.failure(SerieFailure serieFailure) = 8 | _Failure; 9 | const factory FavoriteseriesState.createSuccess() = _CreateSuccess; 10 | const factory FavoriteseriesState.deleteSuccess() = _DeleteSuccess; 11 | const factory FavoriteseriesState.watchSuccess( 12 | List favoriteSeries) = _WatchSuccess; 13 | } 14 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/cast/cast.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'cast.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Cast _$_$_CastFromJson(Map json) { 10 | return _$_Cast( 11 | id: json['id'] as int, 12 | name: json['name'] as String, 13 | profilePath: json['profilePath'] as String, 14 | ); 15 | } 16 | 17 | Map _$_$_CastToJson(_$_Cast instance) => { 18 | 'id': instance.id, 19 | 'name': instance.name, 20 | 'profilePath': instance.profilePath, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/presentation/signin/sign_in_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../application/auth/sign_in/sign_in_bloc.dart'; 5 | import '../../injection.dart'; 6 | import 'widgets/sign_in_scaffold_widget.dart'; 7 | 8 | class SignInScreen extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | backgroundColor: Theme.of(context).backgroundColor, 13 | body: MultiBlocProvider( 14 | providers: [ 15 | BlocProvider( 16 | create: (context) => getIt(), 17 | ), 18 | ], 19 | child: const SignInScaffoldWidget(), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/domain/auth/app_user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'app_user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_AppUser _$_$_AppUserFromJson(Map json) { 10 | return _$_AppUser( 11 | name: json['name'] as String, 12 | photoURL: json['photoURL'] as String, 13 | email: json['email'] as String, 14 | ); 15 | } 16 | 17 | Map _$_$_AppUserToJson(_$_AppUser instance) => 18 | { 19 | 'name': instance.name, 20 | 'photoURL': instance.photoURL, 21 | 'email': instance.email, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/domain/home/series/favorite_serie/favorite_serie.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class FavoriteSerie extends Equatable { 5 | final int favoriteSerieId; 6 | const FavoriteSerie({int favoriteSerieId}) 7 | : favoriteSerieId = favoriteSerieId ?? favoriteSerieId, 8 | super(); 9 | 10 | Map toJson() => {"id": favoriteSerieId}; 11 | 12 | factory FavoriteSerie.fromJsonData(Map json) => 13 | FavoriteSerie(favoriteSerieId: json["id"] as int ?? -0); 14 | 15 | factory FavoriteSerie.fromFirebase(DocumentSnapshot doc) => 16 | FavoriteSerie.fromJsonData(doc.data()); 17 | 18 | @override 19 | List get props => [favoriteSerieId]; 20 | } 21 | -------------------------------------------------------------------------------- /lib/domain/home/movies/favorite_movies/favorite_movies.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class FavoriteMovie extends Equatable { 5 | final int favoriteMovieId; 6 | const FavoriteMovie({int favoriteMovieId}) 7 | : favoriteMovieId = favoriteMovieId ?? favoriteMovieId, 8 | super(); 9 | 10 | Map toJson() => {"id": favoriteMovieId}; 11 | 12 | factory FavoriteMovie.fromJsonData(Map json) => 13 | FavoriteMovie(favoriteMovieId: json["id"] as int ?? -0); 14 | 15 | factory FavoriteMovie.fromFirebase(DocumentSnapshot doc) => 16 | FavoriteMovie.fromJsonData(doc.data()); 17 | 18 | @override 19 | List get props => [favoriteMovieId]; 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.4' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/application/People/people_state.dart: -------------------------------------------------------------------------------- 1 | part of 'people_bloc.dart'; 2 | 3 | @freezed 4 | abstract class PeopleState with _$PeopleState { 5 | const factory PeopleState.initial() = _Initial; 6 | const factory PeopleState.loading() = _Loading; 7 | const factory PeopleState.failure(PeopleFailure peopleFailure) = _Failure; 8 | const factory PeopleState.followSuccess() = _FollowSuccess; 9 | const factory PeopleState.unfollowSuccess() = _UnfollowSuccess; 10 | const factory PeopleState.watchPeopleSuccess(List people) = 11 | _WatchPeopleSuccess; 12 | const factory PeopleState.watchFullPeopleSuccess(List people) = 13 | _WatchFullPeopleSuccess; 14 | const factory PeopleState.loadSuccessforSearchQuery(Person person) = 15 | _LoadSuccessforSearchQuery; 16 | } 17 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/bell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/application/People/people_event.dart: -------------------------------------------------------------------------------- 1 | part of 'people_bloc.dart'; 2 | 3 | @freezed 4 | abstract class PeopleEvent with _$PeopleEvent { 5 | const factory PeopleEvent.followed(String personEmail) = _Followed; 6 | const factory PeopleEvent.unfollowed(String personEmail) = _Unfollowed; 7 | const factory PeopleEvent.watchPeople() = _WatchPeople; 8 | const factory PeopleEvent.peopleRecieved( 9 | Either> failureOrPeople) = _PeopleRecieved; 10 | const factory PeopleEvent.watchFullPeople(String personEmail) = 11 | _WatchFullPeople; 12 | const factory PeopleEvent.fullPeopleRecieved( 13 | Either> failureOrPeople) = 14 | _FullPeopleRecieved; 15 | const factory PeopleEvent.personSearchQuery(String personEmail) = 16 | _PersonSearchQuery; 17 | } 18 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1605131705354 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /assets/icons/regularIcons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | google-services.json 44 | credentials.dart 45 | *android/key.properties 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/domain/home/series/serie_sub/serie_sub.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'serie_sub.freezed.dart'; 5 | 6 | @freezed 7 | abstract class SerieSub implements _$SerieSub { 8 | const factory SerieSub({ 9 | @required int id, 10 | @required String name, 11 | @required String posterPath, 12 | }) = _SerieSub; 13 | 14 | const SerieSub._(); 15 | 16 | factory SerieSub.fromJsonData(Map json) => SerieSub( 17 | id: json['id'] as int ?? 0, 18 | name: json['name'] as String ?? "n/a", 19 | posterPath: json['poster_path'] as String ?? "", 20 | ); 21 | Map toJson() => { 22 | "id": id, 23 | "name": name, 24 | "poster_path": posterPath, 25 | }; 26 | factory SerieSub.fromFirebase(DocumentSnapshot doc) => 27 | SerieSub.fromJsonData(doc.data()); 28 | } 29 | -------------------------------------------------------------------------------- /lib/domain/auth/app_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'app_user.freezed.dart'; 6 | part 'app_user.g.dart'; 7 | 8 | @freezed 9 | abstract class AppUser implements _$AppUser { 10 | const factory AppUser({ 11 | @JsonKey(ignore: true) String id, 12 | @required String name, 13 | @required String photoURL, 14 | @required String email, 15 | }) = _AppUser; 16 | 17 | const AppUser._(); 18 | 19 | AppUser toDomain() { 20 | return AppUser( 21 | id: id, 22 | name: name, 23 | photoURL: photoURL, 24 | email: email, 25 | ); 26 | } 27 | 28 | factory AppUser.fromJson(Map json) => 29 | _$AppUserFromJson(json); 30 | 31 | factory AppUser.fromFirebase(DocumentSnapshot doc) { 32 | return AppUser.fromJson(doc.data()).copyWith(id: doc.id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movie_sub/movie_sub.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'movie_sub.freezed.dart'; 5 | 6 | @freezed 7 | abstract class MovieSub implements _$MovieSub { 8 | const factory MovieSub({ 9 | @required int id, 10 | @required String title, 11 | @required String posterPath, 12 | }) = _MovieSub; 13 | 14 | const MovieSub._(); 15 | 16 | factory MovieSub.fromJsonData(Map json) => MovieSub( 17 | id: json['id'] as int ?? 0, 18 | title: json['title'] as String ?? "n/a", 19 | posterPath: json['poster_path'] as String ?? "", 20 | ); 21 | 22 | Map toJson() => { 23 | "id": id, 24 | "title": title, 25 | "poster_path": posterPath, 26 | }; 27 | 28 | factory MovieSub.fromFirebase(DocumentSnapshot doc) => 29 | MovieSub.fromJsonData(doc.data()); 30 | } 31 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movies_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import '../shared_classes/cast/cast.dart'; 4 | import '../shared_classes/cast/cast_failure.dart'; 5 | import '../shared_classes/genres/genre.dart'; 6 | import '../shared_classes/genres/genre_failure.dart'; 7 | import 'movie/movie.dart'; 8 | import 'movie/movies_failure.dart'; 9 | import 'movie_sub/movie_sub.dart'; 10 | 11 | abstract class MoviesInterface { 12 | Future> getMovie(int movieId); 13 | Future>> getMovieListType( 14 | String movieListType); 15 | Future>> getSimilarMovies(int movieId); 16 | 17 | Future>> getGenres(); 18 | Future>> getMovieByGenre( 19 | int movieGenreId); 20 | 21 | Future>> getCast(int movieId); 22 | Future>> getMovieByCastId(int castId); 23 | } 24 | -------------------------------------------------------------------------------- /lib/domain/home/series/series_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import '../shared_classes/cast/cast.dart'; 4 | import '../shared_classes/cast/cast_failure.dart'; 5 | import '../shared_classes/genres/genre.dart'; 6 | import '../shared_classes/genres/genre_failure.dart'; 7 | import 'serie/serie.dart'; 8 | import 'serie/serie_failure.dart'; 9 | import 'serie_sub/serie_sub.dart'; 10 | 11 | abstract class SeriesInterface { 12 | Future> getSerie(int serieId); 13 | Future>> getSerieListType( 14 | String seriesListType); 15 | Future>> getSimilarSeries(int serieId); 16 | 17 | Future>> getGenres(); 18 | Future>> getSerieByGenre( 19 | int serieGenreId); 20 | 21 | Future>> getCast(int serieId); 22 | Future>> getSerieByCastId(int castId); 23 | } 24 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/cancel_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | 5 | import '../app_colors.dart'; 6 | import '../constants/constants.dart'; 7 | 8 | class CancelButton extends StatelessWidget { 9 | const CancelButton({ 10 | Key key, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return IconButton( 16 | onPressed: () => ExtendedNavigator.of(context).pop(), 17 | icon: Container( 18 | height: 32.0, 19 | width: 32.0, 20 | decoration: BoxDecoration( 21 | color: AppColors.white.withOpacity(0.3), 22 | borderRadius: BorderRadius.circular(100.0), 23 | ), 24 | child: SvgPicture.asset( 25 | cancelIcon, 26 | color: AppColors.white, 27 | ), 28 | ), 29 | color: AppColors.white.withOpacity(0.7), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/poster_image_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../infrastructure/core/credentials.dart'; 4 | 5 | class PosterImageWidget extends StatelessWidget { 6 | const PosterImageWidget({ 7 | Key key, 8 | @required this.movieOrSeries, 9 | }) : super(key: key); 10 | 11 | final dynamic movieOrSeries; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Image.network( 16 | "$MOVIE_POSTER_PATH${movieOrSeries.posterPath}", 17 | fit: BoxFit.cover, 18 | errorBuilder: (context, e, error) => 19 | const Center(child: Icon(Icons.image_outlined)), 20 | loadingBuilder: (context, child, loadingProgress) { 21 | if (loadingProgress == null) return child; 22 | return Center( 23 | child: CircularProgressIndicator( 24 | value: loadingProgress.expectedTotalBytes != null 25 | ? loadingProgress.cumulativeBytesLoaded / 26 | loadingProgress.expectedTotalBytes 27 | : null, 28 | ), 29 | ); 30 | }, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nifemi Oluwatimilehin Diffu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/application/home/casts/casts_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../domain/home/shared_classes/cast/cast.dart'; 8 | import '../../../domain/home/shared_classes/cast/cast_failure.dart'; 9 | import '../../../domain/home/shared_classes/cast/cast_interface.dart'; 10 | 11 | part 'casts_bloc.freezed.dart'; 12 | part 'casts_event.dart'; 13 | part 'casts_state.dart'; 14 | 15 | @injectable 16 | class CastsBloc extends Bloc { 17 | final CastInterface castInterface; 18 | CastsBloc(this.castInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | CastsEvent event, 23 | ) async* { 24 | yield* event.map(getCastCalled: (e) async* { 25 | yield const CastsState.loading(); 26 | final failureOrPerson = await castInterface.getPerson(e.castId); 27 | 28 | yield failureOrPerson.fold( 29 | (f) => CastsState.loadFailure(f), 30 | (person) => CastsState.loadSuccess(person), 31 | ); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/age_restriction_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../app_colors.dart'; 4 | 5 | class AgeRestrictionWidget extends StatelessWidget { 6 | final String age; 7 | const AgeRestrictionWidget({ 8 | Key key, 9 | @required this.age, 10 | }) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final appTextTheme = Theme.of(context).textTheme; 15 | return Container( 16 | height: 22.0, 17 | decoration: BoxDecoration( 18 | borderRadius: BorderRadius.circular(3.0), 19 | border: Border.all(color: AppColors.red.withOpacity(0.9)), 20 | color: AppColors.red.withOpacity(0.9), 21 | ), 22 | child: Padding( 23 | padding: const EdgeInsets.all(2.0), 24 | child: Text( 25 | age, 26 | style: TextStyle( 27 | fontWeight: FontWeight.bold, 28 | fontSize: 16.0, 29 | fontFamily: appTextTheme.bodyText1.fontFamily, 30 | color: AppColors.white, 31 | ), 32 | textAlign: TextAlign.center, 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/application/home/series/casts/casts_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../../domain/home/series/series_interface.dart'; 8 | import '../../../../domain/home/shared_classes/cast/cast.dart'; 9 | import '../../../../domain/home/shared_classes/cast/cast_failure.dart'; 10 | 11 | part 'casts_bloc.freezed.dart'; 12 | part 'casts_event.dart'; 13 | part 'casts_state.dart'; 14 | 15 | @injectable 16 | class CastsBloc extends Bloc { 17 | final SeriesInterface seriesInterface; 18 | CastsBloc(this.seriesInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | CastsEvent event, 23 | ) async* { 24 | yield* event.map(getCastCalled: (e) async* { 25 | yield const CastsState.loading(); 26 | final failureOrSeries = await seriesInterface.getCast(e.serieId); 27 | 28 | yield failureOrSeries.fold( 29 | (f) => CastsState.loadFailure(f), 30 | (casts) => CastsState.loadSuccess(casts), 31 | ); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/application/home/movies/casts/casts_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../../domain/home/movies/movies_interface.dart'; 8 | import '../../../../domain/home/shared_classes/cast/cast.dart'; 9 | import '../../../../domain/home/shared_classes/cast/cast_failure.dart'; 10 | 11 | part 'casts_bloc.freezed.dart'; 12 | part 'casts_event.dart'; 13 | part 'casts_state.dart'; 14 | 15 | @injectable 16 | class CastsBloc extends Bloc { 17 | final MoviesInterface moviesInterface; 18 | 19 | CastsBloc(this.moviesInterface) : super(const _Initial()); 20 | 21 | @override 22 | Stream mapEventToState( 23 | CastsEvent event, 24 | ) async* { 25 | yield* event.map(getCastCalled: (e) async* { 26 | yield const CastsState.loading(); 27 | final failureOrMovies = await moviesInterface.getCast(e.movieId); 28 | 29 | yield failureOrMovies.fold( 30 | (f) => CastsState.loadFailure(f), 31 | (casts) => CastsState.loadSuccess(casts), 32 | ); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/saving_in_progress_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:movingPictures/presentation/core/app_colors.dart'; 3 | 4 | class SavingInProgressOverlay extends StatelessWidget { 5 | final bool isSaving; 6 | const SavingInProgressOverlay({ 7 | Key key, 8 | @required this.isSaving, 9 | }) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return IgnorePointer( 14 | ignoring: !isSaving, 15 | child: AnimatedContainer( 16 | duration: const Duration(milliseconds: 150), 17 | color: isSaving ? Colors.black.withOpacity(0.6) : Colors.transparent, 18 | width: MediaQuery.of(context).size.width, 19 | height: MediaQuery.of(context).size.height, 20 | child: Visibility( 21 | visible: isSaving, 22 | child: Column( 23 | mainAxisAlignment: MainAxisAlignment.center, 24 | children: const [ 25 | CircularProgressIndicator( 26 | backgroundColor: AppColors.white, 27 | ), 28 | ], 29 | ), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/presentation/search/search_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../application/search/search_bloc.dart'; 5 | import '../../injection.dart'; 6 | import '../core/app_colors.dart'; 7 | import 'widgets/search_bar.dart'; 8 | import 'widgets/search_result_or_trending.dart'; 9 | 10 | class SearchScreen extends StatefulWidget { 11 | @override 12 | _SearchScreenState createState() => _SearchScreenState(); 13 | } 14 | 15 | class _SearchScreenState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return BlocProvider( 19 | create: (context) => 20 | getIt()..add(const SearchEvent.trendingCalled()), 21 | child: BlocBuilder( 22 | buildWhen: (previous, current) => previous != current, 23 | builder: (context, state) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | backgroundColor: AppColors.gray, 27 | title: const SearchBar(), 28 | ), 29 | body: const SearchResultsORTrending(), 30 | ); 31 | }, 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/domain/core/value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:uuid/uuid.dart'; 4 | 5 | import 'errors.dart'; 6 | import 'failures.dart'; 7 | 8 | @immutable 9 | abstract class ValueObject { 10 | const ValueObject(); 11 | Either, T> get value; 12 | 13 | ///Throws [UnexpectedValueError] containing the [ValueFailure] 14 | T getOrCrash() { 15 | // id = identity - same as writing (right) => right 16 | return value.fold((f) => throw UnexpectedValueError(f), id); 17 | } 18 | 19 | Either, Unit> get failureOrUnit { 20 | return value.fold((l) => left(l), (r) => right(unit)); 21 | } 22 | 23 | bool isvalid() => value.isRight(); 24 | 25 | List get props => [value]; 26 | } 27 | 28 | class UniqueId extends ValueObject { 29 | @override 30 | final Either, String> value; 31 | 32 | factory UniqueId() { 33 | return UniqueId._(right(Uuid().v1())); 34 | } 35 | 36 | factory UniqueId.fromUniqueString(String uniqueId) { 37 | assert(uniqueId != null); 38 | return UniqueId._(right(uniqueId)); 39 | } 40 | 41 | const UniqueId._(this.value); 42 | } 43 | -------------------------------------------------------------------------------- /lib/domain/home/series/serie/content_ratings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class ContentRatings { 4 | ContentRatings({ 5 | @required this.results, 6 | }); 7 | 8 | final List results; 9 | 10 | factory ContentRatings.fromJson(Map json) => ContentRatings( 11 | results: List.from(json["results"].map( 12 | (x) => ContentRatingsResult.fromJson(x as Map)) 13 | as Iterable), 14 | ); 15 | 16 | Map toJson() => { 17 | "results": List.from(results.map((x) => x.toJson())), 18 | }; 19 | } 20 | 21 | class ContentRatingsResult { 22 | ContentRatingsResult({ 23 | @required this.iso31661, 24 | @required this.rating, 25 | }); 26 | 27 | final String iso31661; 28 | final String rating; 29 | 30 | factory ContentRatingsResult.fromJson(Map json) => 31 | ContentRatingsResult( 32 | iso31661: json["iso_3166_1"] as String, 33 | rating: json["rating"] as String, 34 | ); 35 | 36 | Map toJson() => { 37 | "iso_3166_1": iso31661, 38 | "rating": rating, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /lib/application/home/series/genres/genres_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | import 'package:movingPictures/domain/home/series/series_interface.dart'; 7 | 8 | import '../../../../domain/home/shared_classes/genres/genre.dart'; 9 | import '../../../../domain/home/shared_classes/genres/genre_failure.dart'; 10 | 11 | part 'genres_bloc.freezed.dart'; 12 | part 'genres_event.dart'; 13 | part 'genres_state.dart'; 14 | 15 | @injectable 16 | class GenresBloc extends Bloc { 17 | final SeriesInterface seriesInterface; 18 | GenresBloc(this.seriesInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | GenresEvent event, 23 | ) async* { 24 | yield* event.map( 25 | getGenresCalled: (_) async* { 26 | yield const GenresState.loading(); 27 | final failureOrGenres = await seriesInterface.getGenres(); 28 | 29 | yield failureOrGenres.fold( 30 | (f) => GenresState.loadFailure(f), 31 | (genres) => GenresState.loadSuccess(genres), 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/application/home/movies/genres/genres_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | import 'package:movingPictures/domain/home/shared_classes/genres/genre.dart'; 7 | import 'package:movingPictures/domain/home/shared_classes/genres/genre_failure.dart'; 8 | import '../../../../domain/home/movies/movies_interface.dart'; 9 | 10 | part 'genres_bloc.freezed.dart'; 11 | part 'genres_event.dart'; 12 | part 'genres_state.dart'; 13 | 14 | @injectable 15 | class GenresBloc extends Bloc { 16 | final MoviesInterface moviesInterface; 17 | 18 | GenresBloc(this.moviesInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | GenresEvent event, 23 | ) async* { 24 | yield* event.map( 25 | getGenresCalled: (_) async* { 26 | yield const GenresState.loading(); 27 | final failureOrGenres = await moviesInterface.getGenres(); 28 | 29 | yield failureOrGenres.fold( 30 | (f) => GenresState.loadFailure(f), 31 | (genres) => GenresState.loadSuccess(genres), 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/application/auth/auth_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../domain/auth/auth_repository_interface.dart'; 8 | 9 | part 'auth_bloc.freezed.dart'; 10 | part 'auth_event.dart'; 11 | part 'auth_state.dart'; 12 | 13 | @injectable 14 | class AuthBloc extends Bloc { 15 | final AuthInterface _authInterface; 16 | 17 | @override 18 | AuthBloc(this._authInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | AuthEvent event, 23 | ) async* { 24 | yield* event.map( 25 | authCheckRequested: (e) async* { 26 | final userOption = await _authInterface.getSignedInUser(); 27 | 28 | yield userOption.fold( 29 | () => const AuthState.unAuthenticated(), 30 | (_) => const AuthState.authenticated(), 31 | ); 32 | }, 33 | signedOut: (e) async* { 34 | await _authInterface.signOut(); 35 | yield const AuthState.unAuthenticated(); 36 | }, 37 | storeGoogleUser: (e) async* { 38 | await _authInterface.storeGoogleUser(); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/application/auth/sign_in/sign_in_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:freezed_annotation/freezed_annotation.dart'; 7 | import 'package:injectable/injectable.dart'; 8 | 9 | import '../../../domain/auth/auth_failure.dart'; 10 | import '../../../domain/auth/auth_repository_interface.dart'; 11 | 12 | part 'sign_in_bloc.freezed.dart'; 13 | part 'sign_in_event.dart'; 14 | part 'sign_in_state.dart'; 15 | 16 | @injectable 17 | class SignInBloc extends Bloc { 18 | final AuthInterface _authInterface; 19 | 20 | SignInBloc( 21 | this._authInterface, 22 | ) : super(SignInState.initial()); 23 | 24 | @override 25 | Stream mapEventToState( 26 | SignInEvent event, 27 | ) async* { 28 | yield* event.map( 29 | signInwithGooglePressed: (e) async* { 30 | yield state.copyWith( 31 | isSubmitting: true, 32 | authFailureOrSuccessOption: none(), 33 | ); 34 | 35 | final failureOrSuccess = await _authInterface.signInWithGoogle(); 36 | 37 | yield state.copyWith( 38 | isSubmitting: false, 39 | authFailureOrSuccessOption: some(failureOrSuccess), 40 | ); 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/infrastructure/core/firestore_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | import '../../domain/auth/auth_repository_interface.dart'; 4 | import '../../domain/core/errors.dart'; 5 | import '../../injection.dart'; 6 | 7 | extension FirestoreX on FirebaseFirestore { 8 | Future userDocument() async { 9 | final userOption = await getIt().getSignedInUser(); 10 | final user = userOption.getOrElse(() => throw NotAuthenticatedError()); 11 | 12 | return FirebaseFirestore.instance.collection('users').doc(user.email); 13 | } 14 | 15 | Future favoriteMovieDocument(int movieId) async { 16 | final userDoc = await userDocument(); 17 | 18 | return userDoc.collection('movies').doc(movieId.toString()); 19 | } 20 | 21 | Future favoriteSerieDocument(int serieId) async { 22 | final userDoc = await userDocument(); 23 | 24 | return userDoc.collection('series').doc(serieId.toString()); 25 | } 26 | 27 | Future peopleDocument(String personEmail) async { 28 | final userDoc = await userDocument(); 29 | 30 | return userDoc.collection('people').doc(personEmail); 31 | } 32 | } 33 | 34 | // extension DocumentReferenceX on DocumentReference { 35 | // CollectionReference get userDataCollection => collection('userdata'); 36 | // } 37 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/movie_loading_wigdet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | import '../app_colors.dart'; 5 | 6 | class MovieLoadingWidget extends StatelessWidget { 7 | const MovieLoadingWidget({ 8 | Key key, 9 | }) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Shimmer.fromColors( 14 | baseColor: AppColors.gray.withOpacity(0.2), 15 | highlightColor: AppColors.white.withOpacity(0.5), 16 | child: Container( 17 | margin: const EdgeInsets.all(20.0), 18 | decoration: BoxDecoration( 19 | borderRadius: BorderRadius.circular(20.0), 20 | color: AppColors.gray, 21 | ), 22 | height: MediaQuery.of(context).size.height / 5, 23 | ), 24 | ); 25 | } 26 | } 27 | 28 | class MovieErrorWidget extends StatelessWidget { 29 | const MovieErrorWidget({ 30 | Key key, 31 | }) : super(key: key); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Container( 36 | decoration: BoxDecoration( 37 | color: AppColors.gray, 38 | borderRadius: BorderRadius.circular(5.0), 39 | ), 40 | child: const Center( 41 | child: Icon( 42 | Icons.error_outline, 43 | color: AppColors.red, 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/domain/home/shared_classes/videos.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Videos { 4 | Videos({ 5 | @required this.results, 6 | }); 7 | 8 | final List results; 9 | 10 | factory Videos.fromJson(Map json) => Videos( 11 | results: List.from( 12 | json["results"].map( 13 | (x) => Result.fromJson(x as Map), 14 | ) as Iterable, 15 | ), 16 | ); 17 | 18 | Map toJson() => { 19 | "results": List.from(results.map((x) => x.toJson())), 20 | }; 21 | } 22 | 23 | class Result { 24 | Result({ 25 | @required this.id, 26 | @required this.key, 27 | @required this.name, 28 | @required this.site, 29 | @required this.type, 30 | }); 31 | 32 | final String id; 33 | final String key; 34 | final String name; 35 | final String site; 36 | final String type; 37 | 38 | factory Result.fromJson(Map json) => Result( 39 | id: json["id"] as String, 40 | key: json["key"] as String, 41 | name: json["name"] as String, 42 | site: json["site"] as String, 43 | type: json["type"] as String, 44 | ); 45 | 46 | Map toJson() => { 47 | "id": id, 48 | "key": key, 49 | "name": name, 50 | "site": site, 51 | "type": type, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /lib/presentation/splash/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../application/auth/auth_bloc.dart'; 7 | import '../core/constants/constants.dart'; 8 | import '../routes/router.gr.dart'; 9 | 10 | class SplashScreen extends StatelessWidget { 11 | const SplashScreen({Key key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return BlocListener( 16 | listener: (context, state) { 17 | state.map( 18 | initial: (_) {}, 19 | authenticated: (_) => 20 | ExtendedNavigator.of(context).replace(Routes.mainBodyLayout), 21 | unAuthenticated: (_) => 22 | ExtendedNavigator.of(context).replace(Routes.signInScreen), 23 | ); 24 | }, 25 | child: AnnotatedRegion( 26 | value: const SystemUiOverlayStyle( 27 | statusBarColor: Colors.transparent, 28 | statusBarIconBrightness: Brightness.light, 29 | ), 30 | child: Scaffold( 31 | body: Center( 32 | child: Image.asset( 33 | movingPicturesLogoRed, 34 | width: 250.0, 35 | ), 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/infrastructure/home/casts/casts_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../domain/home/shared_classes/cast/cast.dart'; 8 | import '../../../domain/home/shared_classes/cast/cast_failure.dart'; 9 | import '../../../domain/home/shared_classes/cast/cast_interface.dart'; 10 | import '../../core/credentials.dart'; 11 | 12 | @LazySingleton(as: CastInterface) 13 | class CastRepository extends CastInterface { 14 | final Dio _dio = Dio(); 15 | final String apiKey = TMDB_API_KEY; 16 | final String tmdbUrl = TMDB_URL; 17 | String deviceLocal = Platform.localeName; 18 | 19 | @override 20 | Future> getPerson(int castId) async { 21 | if (deviceLocal == "pt_BR") deviceLocal = "pt-BR"; 22 | if (deviceLocal == "en_US") deviceLocal = "en-US"; 23 | 24 | final getPersonUrl = "$tmdbUrl/person/$castId"; 25 | final params = { 26 | "api_key": apiKey, 27 | "language": deviceLocal, 28 | }; 29 | 30 | try { 31 | final Response> response = await _dio.get( 32 | getPersonUrl, 33 | queryParameters: params, 34 | ); 35 | final Cast movie = Cast.fromJson(response.data); 36 | 37 | return right(movie); 38 | } catch (e) { 39 | return left(const CastFailure.unexpected()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/presentation/routes/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route_annotations.dart'; 2 | 3 | import '../favorites/favorites.dart'; 4 | import '../home/movies/cast_movies_screen.dart'; 5 | import '../home/movies/genre_movies_screen.dart'; 6 | import '../home/movies/movie_info/movie_info.dart'; 7 | import '../home/series/cast_series_screen.dart'; 8 | import '../home/series/genre_series_screen.dart'; 9 | import '../home/series/serie_info/serie_info.dart'; 10 | import '../main_layout_appbar_navbar/main_body_layout.dart'; 11 | import '../profile/profile_screen.dart'; 12 | import '../signin/sign_in_screen.dart'; 13 | import '../splash/splash_screen.dart'; 14 | 15 | @MaterialAutoRouter( 16 | routes: [ 17 | CupertinoRoute(page: SplashScreen, initial: true), 18 | CupertinoRoute(page: SignInScreen), 19 | CupertinoRoute(page: MainBodyLayout), 20 | CupertinoRoute(page: ProfileScreen, fullscreenDialog: true), 21 | CupertinoRoute(page: MovieInfo, fullscreenDialog: true), 22 | CupertinoRoute(page: SerieInfo, fullscreenDialog: true), 23 | CupertinoRoute(page: GenreMoviesScreen, fullscreenDialog: true), 24 | CupertinoRoute(page: GenreSeriesScreen, fullscreenDialog: true), 25 | CupertinoRoute(page: CastMoviesScreen, fullscreenDialog: true), 26 | CupertinoRoute(page: CastSeriesScreen, fullscreenDialog: true), 27 | CupertinoRoute(page: Favorites, fullscreenDialog: true), 28 | ], 29 | generateNavigationHelperExtension: true, 30 | ) 31 | class $AppRouter {} 32 | -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "signInWelcomeMessage": "All of the worlds movies and series in one place.", 4 | "signInButton": "Sign in with Google", 5 | 6 | "home": "Home", 7 | "search": "Search", 8 | "favorites": "Favorites", 9 | "people": "People", 10 | 11 | "signedInWithGoogle": "Signed in with Google", 12 | "signOut": "Sign out", 13 | "questionMark": "?", 14 | "cancel": "Cancel", 15 | "availableOnGithub": "This project is Open-Source and available on GitHub.", 16 | "seeOnGithub": "See on GitHub", 17 | "notEndorsedByTMDB": "This project uses the TMDB API but is NOT endorsed or certified by TMDB.", 18 | "goToTMDB": "Go to TMDB", 19 | 20 | "movies": "Movies", 21 | "series": "Series", 22 | 23 | "top10": "Top 10", 24 | "horror": "Horror", 25 | "action": "Action", 26 | "comedy": "Comedy", 27 | "trendingNow": "Trending Now", 28 | "upcoming": "Upcoming", 29 | "latest": "Latest", 30 | "nifemiRecommends": "Recommended", 31 | 32 | "moreInfo": "More info", 33 | "info": "Info", 34 | "share": "Share", 35 | "favorite": "Favorite", 36 | "favorited": "Favorited", 37 | "watchTrailer": "Watch trailer", 38 | "visitHome": "Visit home page", 39 | "starring": "Starring", 40 | "more": "more", 41 | "morelikethis": "MORE LIKE THIS", 42 | "noHomePage": "No homepage available for this movie.", 43 | "noTrailer": "No Trailer available for this movie.", 44 | "cast": "Cast", 45 | 46 | "cancelled":"Cancelled", 47 | "oopsServerError":"Oops! Something went wrong", 48 | "unexpectedError":"Unexpected Error" 49 | 50 | } 51 | -------------------------------------------------------------------------------- /lib/presentation/search/widgets/search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | 5 | import '../../../application/search/search_bloc.dart'; 6 | import '../../core/app_colors.dart'; 7 | import '../../core/constants/constants.dart'; 8 | 9 | class SearchBar extends StatelessWidget { 10 | const SearchBar({ 11 | Key key, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return TextFormField( 17 | onChanged: (value) => value.isEmpty 18 | ? context.read().add(const SearchEvent.trendingCalled()) 19 | : context.read().add(SearchEvent.queryCalled(value)), 20 | cursorColor: AppColors.white, 21 | keyboardType: TextInputType.text, 22 | textInputAction: TextInputAction.done, 23 | style: const TextStyle(color: AppColors.white, fontSize: 18.0), 24 | decoration: InputDecoration( 25 | prefixIcon: Padding( 26 | padding: const EdgeInsets.all(12.0), 27 | child: SvgPicture.asset(searchIcon, color: Colors.grey)), 28 | contentPadding: const EdgeInsets.symmetric(horizontal: 25.0), 29 | labelText: 'Search for a movie, series or genre', 30 | labelStyle: const TextStyle(color: Colors.grey, fontSize: 14.0), 31 | floatingLabelBehavior: FloatingLabelBehavior.never, 32 | fillColor: AppColors.gray, 33 | filled: true, 34 | border: InputBorder.none, 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/application/search/search_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../domain/home/movies/movie/movies_failure.dart'; 8 | import '../../domain/search/search.dart'; 9 | import '../../domain/search/search_interface.dart'; 10 | 11 | part 'search_bloc.freezed.dart'; 12 | part 'search_event.dart'; 13 | part 'search_state.dart'; 14 | 15 | @injectable 16 | class SearchBloc extends Bloc { 17 | final SearchInterface searchInterface; 18 | SearchBloc(this.searchInterface) : super(const _Initial()); 19 | 20 | @override 21 | Stream mapEventToState( 22 | SearchEvent event, 23 | ) async* { 24 | yield* event.map( 25 | trendingCalled: (e) async* { 26 | yield const SearchState.loading(); 27 | final failureOrMoviesOrSeries = await searchInterface.getTrending(); 28 | 29 | yield failureOrMoviesOrSeries.fold((f) => SearchState.loadFailure(f), 30 | (moviesOrSeries) => SearchState.loadSuccess(moviesOrSeries)); 31 | }, 32 | queryCalled: (e) async* { 33 | yield const SearchState.loading(); 34 | final failureOrMoviesOrSeriesorPerson = 35 | await searchInterface.getSearchQuery(e.query); 36 | 37 | yield failureOrMoviesOrSeriesorPerson.fold( 38 | (f) => SearchState.loadFailure(f), 39 | (moviesOrSeriesOrPerson) => 40 | SearchState.loadSuccessforQuery(moviesOrSeriesOrPerson)); 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lang/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "signInWelcomeMessage": "Todos os filmes e séries do mundo em um só lugar.", 3 | "signInButton": "Faça login no Google", 4 | 5 | "home": "Início", 6 | "search": "Busca", 7 | "favorites": "Favoritos", 8 | "people": "Pessoas", 9 | 10 | "signedInWithGoogle": "Conectado com Google", 11 | "signOut": "Sair", 12 | "questionMark": "?", 13 | "cancel": "Cancelar", 14 | "availableOnGithub": "Este projeto é de código aberto e está disponível no GitHub.", 15 | "seeOnGithub": "Veja no GitHub", 16 | "notEndorsedByTMDB": "Este projeto usa a API TMDB, mas NÃO é endossado ou certificado por TMDB.", 17 | "goToTMDB": "Vá para TMDB", 18 | 19 | "movies": "Filmes", 20 | "series": "Series", 21 | 22 | "top10": "10 Principais", 23 | "horror": "Horror", 24 | "comedy": "Comédia", 25 | "action": "Açao", 26 | "trendingNow": " Tendências Agora", 27 | "upcoming": "Próximos", 28 | "latest": "Mais recentes", 29 | "nifemiRecommends": "Recomendado", 30 | 31 | "moreInfo": "Mais informações", 32 | "info": "Info", 33 | "share": "Compartilhar", 34 | "favorite": "Favorito", 35 | "favorited": "Favoreceu", 36 | "watchTrailer": "Assista o trailer", 37 | "visitHome": "Visite a página inicial", 38 | "starring": "Estrelando", 39 | "more": "Mais", 40 | "morelikethis": "MAIS COMO ISSO", 41 | "noHomePage": "Nenhuma página inicial disponível para este filme.", 42 | "noTrailer": "Nenhuma trailer inicial disponível para este filme.", 43 | "cast": "Elenco", 44 | 45 | 46 | "cancelled": "Cancelado", 47 | "oopsServerError": "Oops! Algo deu errado", 48 | "unexpectedError": "Erro inesperado" 49 | } 50 | -------------------------------------------------------------------------------- /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/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | movingPictures 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movie/release_dates.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class ReleaseDates { 4 | ReleaseDates({ 5 | @required this.results, 6 | }); 7 | 8 | final List results; 9 | 10 | factory ReleaseDates.fromJson(Map json) => ReleaseDates( 11 | results: List.from( 12 | json["results"].map( 13 | (x) => Result.fromJson(x as Map), 14 | ) as Iterable, 15 | ), 16 | ); 17 | 18 | Map toJson() => { 19 | "results": List.from(results.map((x) => x.toJson())), 20 | }; 21 | } 22 | 23 | class Result { 24 | Result({ 25 | @required this.releaseDates, 26 | @required this.iso31661, 27 | }); 28 | 29 | final List releaseDates; 30 | final String iso31661; 31 | 32 | factory Result.fromJson(Map json) => Result( 33 | iso31661: json["iso_3166_1"] as String, 34 | releaseDates: List.from( 35 | json["release_dates"].map( 36 | (x) => ReleaseDate.fromJson(x as Map), 37 | ) as Iterable, 38 | ), 39 | ); 40 | 41 | Map toJson() => { 42 | "iso_3166_1": iso31661, 43 | "release_dates": 44 | List.from(releaseDates.map((x) => x.toJson())), 45 | }; 46 | } 47 | 48 | class ReleaseDate { 49 | ReleaseDate({ 50 | @required this.certification, 51 | }); 52 | 53 | final String certification; 54 | 55 | factory ReleaseDate.fromJson(Map json) => ReleaseDate( 56 | certification: json["certification"] as String, 57 | ); 58 | 59 | Map toJson() => { 60 | "certification": certification, 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movie/movie.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'movie.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Movie _$_$_MovieFromJson(Map json) { 10 | return _$_Movie( 11 | id: json['id'] as int, 12 | title: json['title'] as String, 13 | posterPath: json['posterPath'] as String, 14 | overview: json['overview'] as String, 15 | releaseDate: json['releaseDate'] as String, 16 | runtime: json['runtime'] as int, 17 | releaseDates: json['releaseDates'] == null 18 | ? null 19 | : ReleaseDates.fromJson(json['releaseDates'] as Map), 20 | voteAverage: (json['voteAverage'] as num)?.toDouble(), 21 | genres: (json['genres'] as List) 22 | ?.map( 23 | (e) => e == null ? null : Genre.fromJson(e as Map)) 24 | ?.toList(), 25 | homepage: json['homepage'] as String, 26 | video: json['video'] == null 27 | ? null 28 | : Videos.fromJson(json['video'] as Map), 29 | ); 30 | } 31 | 32 | Map _$_$_MovieToJson(_$_Movie instance) => { 33 | 'id': instance.id, 34 | 'title': instance.title, 35 | 'posterPath': instance.posterPath, 36 | 'overview': instance.overview, 37 | 'releaseDate': instance.releaseDate, 38 | 'runtime': instance.runtime, 39 | 'releaseDates': instance.releaseDates?.toJson(), 40 | 'voteAverage': instance.voteAverage, 41 | 'genres': instance.genres?.map((e) => e?.toJson())?.toList(), 42 | 'homepage': instance.homepage, 43 | 'video': instance.video?.toJson(), 44 | }; 45 | -------------------------------------------------------------------------------- /lib/presentation/core/component_widgets/primary_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../app_colors.dart'; 5 | 6 | class PrimaryButton extends StatelessWidget { 7 | final dynamic state; 8 | final TextTheme appTextTheme; 9 | final String name; 10 | final Color color; 11 | final Function onpressed; 12 | final bool isFullButton; 13 | final Widget icon; 14 | const PrimaryButton({ 15 | Key key, 16 | this.state, 17 | @required this.appTextTheme, 18 | @required this.name, 19 | @required this.color, 20 | @required this.onpressed, 21 | @required this.isFullButton, 22 | this.icon, 23 | }) : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return SizedBox( 28 | width: isFullButton 29 | ? MediaQuery.of(context).size.width 30 | : MediaQuery.of(context).size.width / 1.9, 31 | child: RaisedButton( 32 | padding: const EdgeInsets.all(8.0), 33 | onPressed: () => onpressed(), 34 | color: color, 35 | child: Row( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | children: [ 38 | if (icon != null) 39 | Row(children: [icon, const SizedBox(width: 5.0)]) 40 | else 41 | Container(), 42 | Text( 43 | name, 44 | style: TextStyle( 45 | fontSize: appTextTheme.button.fontSize, 46 | fontWeight: appTextTheme.button.fontWeight, 47 | color: color != AppColors.white 48 | ? AppColors.white 49 | : AppColors.black, 50 | ), 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/application/auth/user_profile/user_profile_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | import 'package:injectable/injectable.dart'; 7 | 8 | import '../../../domain/auth/app_user.dart'; 9 | import '../../../domain/auth/app_user_failure.dart'; 10 | import '../../../domain/auth/auth_repository_interface.dart'; 11 | 12 | part 'user_profile_bloc.freezed.dart'; 13 | part 'user_profile_event.dart'; 14 | part 'user_profile_state.dart'; 15 | 16 | @injectable 17 | class UserProfileBloc extends Bloc { 18 | final AuthInterface _authInterface; 19 | 20 | StreamSubscription> 21 | _appUserStreamSubscription; 22 | 23 | UserProfileBloc(this._authInterface) : super(const _Initial()); 24 | 25 | @override 26 | Stream mapEventToState( 27 | UserProfileEvent event, 28 | ) async* { 29 | yield* event.map( 30 | watchProfileStarted: (e) async* { 31 | yield const UserProfileState.loadingProgress(); 32 | 33 | await _appUserStreamSubscription?.cancel(); 34 | _appUserStreamSubscription = 35 | _authInterface.watchUserProfile().listen((failureOrProfile) => add( 36 | UserProfileEvent.profileRecieved(failureOrProfile), 37 | )); 38 | }, 39 | profileRecieved: (_ProfileRecieved e) async* { 40 | yield e.failureOrProfile.fold( 41 | (f) => UserProfileState.loadFailure(f), 42 | (profile) => UserProfileState.loadSuccess(profile), 43 | ); 44 | }, 45 | ); 46 | } 47 | 48 | @override 49 | Future close() async { 50 | await _appUserStreamSubscription.cancel(); 51 | return super.close(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/presentation/core/constants/language_constants.dart: -------------------------------------------------------------------------------- 1 | //* SignIn Screen 2 | const String signInWelcomeMessage = 'signInWelcomeMessage'; 3 | const String signInButton = "signInButton"; 4 | 5 | //* Bottom Navigation Bar 6 | const String home = "home"; 7 | const String search = "search"; 8 | const String favorites = "favorites"; 9 | const String people = "people"; 10 | 11 | //* Profile Screen 12 | const String signedInWithGoogle = "signedInWithGoogle"; 13 | const String signOut = "signOut"; 14 | const String questionMark = "questionMark"; 15 | const String cancel = "cancel"; 16 | const String availableOnGithub = "availableOnGithub"; 17 | const String seeOnGithub = "seeOnGithub"; 18 | const String notEndorsedByTMDB = "notEndorsedByTMDB"; 19 | const String goToTMDB = "goToTMDB"; 20 | 21 | //* Tabs 22 | const String movies = "movies"; 23 | const String series = "series"; 24 | 25 | //* Blocks 26 | const String top10 = "top10"; 27 | const String horror = "horror"; 28 | const String action = "action"; 29 | const String comedy = "comedy"; 30 | const String trendingNow = "trendingNow"; 31 | const String upcoming = "upcoming"; 32 | const String latest = "latest"; 33 | const String nifemiRecommends = "nifemiRecommends"; 34 | 35 | //* Info Bottomsheet 36 | const String moreInfo = "moreInfo"; 37 | const String info = "info"; 38 | const String share = "share"; 39 | const String favorite = "favorite"; 40 | const String favorited = "favorited"; 41 | const String watchTrailer = "watchTrailer"; 42 | const String visitHome = "visitHome"; 43 | const String starring = "starring"; 44 | const String more = "more"; 45 | const String morelikethis = "morelikethis"; 46 | const String noHomePage = "noHomePage"; 47 | const String noTrailer = "noTrailer"; 48 | const String cast = "cast"; 49 | 50 | //* Errors 51 | const cancelled = "cancelled"; 52 | const oopsServerError = "oopsServerError"; 53 | const unexpectedError = "unexpectedError"; 54 | -------------------------------------------------------------------------------- /lib/domain/people/people.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | class Person { 5 | final String email; 6 | final String name; 7 | final String photoURL; 8 | // final List favoriteMovies; 9 | // final List favoriteSeries; 10 | 11 | Person({ 12 | @required this.email, 13 | @required this.name, 14 | @required this.photoURL, 15 | // @required this.favoriteMovies, 16 | // @required this.favoriteSeries, 17 | }); 18 | 19 | factory Person.fromJsonData(Map json) => Person( 20 | email: json['email'] as String, 21 | name: json['name'] as String, 22 | photoURL: json['photoURL'] as String, 23 | // favoriteMovies: List.from(json['movies'].map( 24 | // (x) => PersonFavMovie.fromJsonData(x as Map), 25 | // ) as Iterable), 26 | // favoriteSeries: List.from(json['series'].map( 27 | // (x) => PersonFavSerie.fromJsonData(x as Map), 28 | // ) as Iterable), 29 | ); 30 | 31 | Map toJson() => { 32 | "email": email, 33 | "name": name, 34 | "photoURL": photoURL, 35 | }; 36 | 37 | factory Person.fromFirebase(DocumentSnapshot doc) => 38 | Person.fromJsonData(doc.data()); 39 | } 40 | 41 | // class PersonFavMovie { 42 | // final int favoriteMovieId; 43 | 44 | // PersonFavMovie({@required this.favoriteMovieId}); 45 | 46 | // factory PersonFavMovie.fromJsonData(Map json) => 47 | // PersonFavMovie(favoriteMovieId: json["id"] as int ?? -0); 48 | // } 49 | 50 | // class PersonFavSerie { 51 | // final int favoriteSerieId; 52 | 53 | // PersonFavSerie({@required this.favoriteSerieId}); 54 | 55 | // factory PersonFavSerie.fromJsonData(Map json) => 56 | // PersonFavSerie(favoriteSerieId: json["id"] as int ?? -0); 57 | // } 58 | -------------------------------------------------------------------------------- /lib/presentation/home/movies/movie_info/widgets/genres_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../../../domain/home/movies/movie/movie.dart'; 5 | import '../../../../core/app_colors.dart'; 6 | import '../../../../routes/router.gr.dart'; 7 | 8 | class GenresList extends StatelessWidget { 9 | final Movie movie; 10 | const GenresList({ 11 | Key key, 12 | @required this.movie, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final appTextTheme = Theme.of(context).textTheme; 18 | 19 | return Padding( 20 | padding: const EdgeInsets.symmetric(horizontal: 40.0), 21 | child: Wrap( 22 | alignment: WrapAlignment.center, 23 | runSpacing: 8.0, 24 | spacing: 0.8, 25 | children: movie.genres 26 | .map((genre) => GestureDetector( 27 | onTap: () => ExtendedNavigator.of(context) 28 | .pushGenreMoviesScreen(genre: genre), 29 | child: Container( 30 | padding: const EdgeInsets.symmetric( 31 | horizontal: 5.0, 32 | vertical: 2.0, 33 | ), 34 | margin: const EdgeInsets.symmetric(horizontal: 2.5), 35 | decoration: BoxDecoration( 36 | border: Border.all(color: AppColors.white), 37 | borderRadius: BorderRadius.circular(3.0), 38 | ), 39 | child: Text(genre.name, 40 | style: TextStyle( 41 | fontFamily: appTextTheme.subtitle1.fontFamily, 42 | fontWeight: FontWeight.w600, 43 | color: appTextTheme.subtitle1.color, 44 | )), 45 | ), 46 | )) 47 | .toList(), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/presentation/home/series/serie_info/widgets/genres_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../../../domain/home/series/serie/serie.dart'; 5 | import '../../../../core/app_colors.dart'; 6 | import '../../../../routes/router.gr.dart'; 7 | 8 | class GenresList extends StatelessWidget { 9 | final Serie serie; 10 | const GenresList({ 11 | Key key, 12 | @required this.serie, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final appTextTheme = Theme.of(context).textTheme; 18 | 19 | return Padding( 20 | padding: const EdgeInsets.symmetric(horizontal: 40.0), 21 | child: Wrap( 22 | alignment: WrapAlignment.center, 23 | runSpacing: 8.0, 24 | spacing: 0.8, 25 | children: serie.genres 26 | .map((genre) => GestureDetector( 27 | onTap: () => ExtendedNavigator.of(context) 28 | .pushGenreSeriesScreen(genre: genre), 29 | child: Container( 30 | padding: const EdgeInsets.symmetric( 31 | horizontal: 5.0, 32 | vertical: 2.0, 33 | ), 34 | margin: const EdgeInsets.symmetric(horizontal: 2.5), 35 | decoration: BoxDecoration( 36 | border: Border.all(color: AppColors.white), 37 | borderRadius: BorderRadius.circular(3.0), 38 | ), 39 | child: Text(genre.name, 40 | style: TextStyle( 41 | fontFamily: appTextTheme.subtitle1.fontFamily, 42 | fontWeight: FontWeight.w600, 43 | color: appTextTheme.subtitle1.color, 44 | )), 45 | ), 46 | )) 47 | .toList(), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/presentation/home/movies/movies_tab_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../application/home/movies/movies/movies_bloc.dart'; 4 | import '../../core/constants/language_constants.dart'; 5 | import 'widgets/all_genres_block.dart'; 6 | import 'widgets/banner_block_widget.dart'; 7 | import 'widgets/regular_block_widget.dart'; 8 | import 'widgets/top_10_block_widget.dart'; 9 | 10 | class MoviesTabScreen extends StatelessWidget { 11 | const MoviesTabScreen({Key key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SingleChildScrollView( 16 | child: Column( 17 | children: const [ 18 | BannerBlockWidget(), 19 | SizedBox(height: 20.0), 20 | RegularBlockWidget( 21 | blockName: upcoming, 22 | moviesEvent: MoviesEvent.movieTypeCalled("upcoming"), 23 | ), 24 | SizedBox(height: 20.0), 25 | RegularBlockWidget( 26 | blockName: trendingNow, 27 | moviesEvent: MoviesEvent.movieTypeCalled("popular"), 28 | ), 29 | SizedBox(height: 20.0), 30 | TopTenBlockWidget( 31 | moviesEvent: MoviesEvent.movieTypeCalled("top_rated"), 32 | ), 33 | SizedBox(height: 20.0), 34 | RegularBlockWidget( 35 | blockName: horror, 36 | moviesEvent: MoviesEvent.movieByGenreCalled(27), 37 | ), 38 | SizedBox(height: 20.0), 39 | RegularBlockWidget( 40 | blockName: comedy, 41 | moviesEvent: MoviesEvent.movieByGenreCalled(35), 42 | ), 43 | SizedBox(height: 20.0), 44 | RegularBlockWidget( 45 | blockName: action, 46 | moviesEvent: MoviesEvent.movieByGenreCalled(28), 47 | ), 48 | SizedBox(height: 20.0), 49 | AllGenresBlock(), 50 | SizedBox(height: 20.0), 51 | ], 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/presentation/profile/widgets/github_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | import '../../core/app_localizations.dart'; 6 | import '../../core/component_widgets/flushbar_method.dart'; 7 | import '../../core/constants/constants.dart'; 8 | import '../../core/constants/language_constants.dart'; 9 | 10 | class GitHubBlock extends StatelessWidget { 11 | const GitHubBlock({ 12 | Key key, 13 | @required this.appTextTheme, 14 | }) : super(key: key); 15 | 16 | final TextTheme appTextTheme; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final lang = AppLocalizations.of(context); 21 | 22 | // ignore: avoid_void_async 23 | void _launchURL() async { 24 | const url = githubLink; 25 | if (await canLaunch(url)) { 26 | await launch(url); 27 | } else { 28 | showFlushbar( 29 | context: context, message: lang.translate(unexpectedError)); 30 | } 31 | } 32 | 33 | return Center( 34 | child: Column( 35 | children: [ 36 | Text( 37 | lang.translate(availableOnGithub), 38 | style: appTextTheme.headline6, 39 | textAlign: TextAlign.center, 40 | ), 41 | GestureDetector( 42 | onTap: _launchURL, 43 | child: Row( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | SvgPicture.asset( 47 | githubIcon, 48 | color: Colors.blue, 49 | ), 50 | const SizedBox(width: 5.0), 51 | Text( 52 | lang.translate(seeOnGithub), 53 | style: const TextStyle(color: Colors.blue), 54 | ), 55 | const Icon( 56 | Icons.arrow_forward_ios, 57 | color: Colors.blue, 58 | size: 14.0, 59 | ) 60 | ], 61 | ), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/presentation/home/series/series_tab_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:movingPictures/presentation/home/series/widgets/all_genres_block.dart'; 3 | import 'package:movingPictures/presentation/home/series/widgets/top_10_block_widget.dart'; 4 | 5 | import '../../../application/home/series/series/series_bloc.dart'; 6 | import 'widgets/series_banner_block_widget.dart'; 7 | import 'widgets/series_regular_block_widget.dart'; 8 | 9 | class SeriesTabScreen extends StatelessWidget { 10 | const SeriesTabScreen({Key key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return SingleChildScrollView( 15 | physics: const BouncingScrollPhysics(), 16 | child: Column( 17 | children: const [ 18 | SeriesBannerBlockWidget(), 19 | SizedBox(height: 20.0), 20 | SeriesRegularBlockWidget( 21 | blockName: "On The Air", 22 | seriesEvent: SeriesEvent.serieTypeCalled("on_the_air"), 23 | ), 24 | SizedBox(height: 20.0), 25 | SeriesRegularBlockWidget( 26 | blockName: "popular", 27 | seriesEvent: SeriesEvent.serieTypeCalled("popular"), 28 | ), 29 | SizedBox(height: 20.0), 30 | TopTenBlockWidget( 31 | seriesEvent: SeriesEvent.serieTypeCalled("top_rated"), 32 | ), 33 | SizedBox(height: 20.0), 34 | SeriesRegularBlockWidget( 35 | blockName: "Comedy", 36 | seriesEvent: SeriesEvent.serieByGenreCalled(35), 37 | ), 38 | SizedBox(height: 20.0), 39 | SeriesRegularBlockWidget( 40 | blockName: "Drama", 41 | seriesEvent: SeriesEvent.serieByGenreCalled(18), 42 | ), 43 | SizedBox(height: 20.0), 44 | SeriesRegularBlockWidget( 45 | blockName: "Reality", 46 | seriesEvent: SeriesEvent.serieByGenreCalled(10764), 47 | ), 48 | SizedBox(height: 20.0), 49 | AllGenresBlock(), 50 | SizedBox(height: 20.0), 51 | ], 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/presentation/core/app_localizations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart' show rootBundle; 6 | 7 | class AppLocalizations { 8 | final Locale locale; 9 | 10 | AppLocalizations(this.locale); 11 | 12 | //* Helper method to keep the code in the widgets concise 13 | //* Localizations are accessed using an InheritedWidget "of" syntax 14 | static AppLocalizations of(BuildContext context) { 15 | return Localizations.of(context, AppLocalizations); 16 | } 17 | 18 | static const LocalizationsDelegate delegate = 19 | _AppLocalizationsDelegate(); 20 | 21 | Map _localizedStrings; 22 | 23 | Future load() async { 24 | //* Load the language JSON file from the "Lang" folder 25 | final String jsonString = 26 | await rootBundle.loadString('lang/${locale.languageCode}.json'); 27 | 28 | final jsonMap = json.decode(jsonString) as Map; 29 | 30 | _localizedStrings = Map.from(jsonMap); 31 | 32 | return true; 33 | } 34 | 35 | // This method will be called from every widget which needs a localized text 36 | String translate(String key) { 37 | return _localizedStrings[key]; 38 | } 39 | } 40 | 41 | class _AppLocalizationsDelegate 42 | extends LocalizationsDelegate { 43 | // This delegate instance will never change (it doesn't even have fields!) 44 | // It can provide a constant constructor. 45 | const _AppLocalizationsDelegate(); 46 | 47 | @override 48 | bool isSupported(Locale locale) { 49 | // Include all of your supported language codes here 50 | return ['en', 'pt'].contains(locale.languageCode); 51 | } 52 | 53 | @override 54 | Future load(Locale locale) async { 55 | // AppLocalizations class is where the JSON loading actually runs 56 | final AppLocalizations localizations = AppLocalizations(locale); 57 | await localizations.load(); 58 | return localizations; 59 | } 60 | 61 | @override 62 | bool shouldReload(_AppLocalizationsDelegate old) => false; 63 | } 64 | -------------------------------------------------------------------------------- /lib/presentation/people/widgets/people_search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | 5 | import '../../../application/people/people_bloc.dart'; 6 | import '../../core/app_colors.dart'; 7 | import '../../core/constants/constants.dart'; 8 | 9 | class PeopleSearchBar extends StatelessWidget implements PreferredSizeWidget { 10 | const PeopleSearchBar({ 11 | Key key, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Padding( 17 | padding: const EdgeInsets.fromLTRB(10.0, 0.0, 10.0, 10.0), 18 | child: TextFormField( 19 | enabled: false, 20 | onFieldSubmitted: (value) => value.isEmpty 21 | ? context.read().add(const PeopleEvent.watchPeople()) 22 | : context 23 | .read() 24 | .add(PeopleEvent.personSearchQuery(value)), 25 | onChanged: (value) => value.isEmpty 26 | ? context.read().add(const PeopleEvent.watchPeople()) 27 | : null, 28 | cursorColor: AppColors.white, 29 | keyboardType: TextInputType.emailAddress, 30 | textInputAction: TextInputAction.search, 31 | style: const TextStyle(color: AppColors.white, fontSize: 18.0), 32 | decoration: InputDecoration( 33 | border: const OutlineInputBorder( 34 | borderRadius: BorderRadius.all(Radius.circular(10.0))), 35 | prefixIcon: Padding( 36 | padding: const EdgeInsets.all(12.0), 37 | child: SvgPicture.asset(searchIcon, color: Colors.grey)), 38 | contentPadding: const EdgeInsets.symmetric(horizontal: 25.0), 39 | labelText: 'Search for people with email', 40 | labelStyle: const TextStyle(color: Colors.grey, fontSize: 14.0), 41 | floatingLabelBehavior: FloatingLabelBehavior.never, 42 | fillColor: AppColors.gray, 43 | filled: true, 44 | ), 45 | ), 46 | ); 47 | } 48 | 49 | @override 50 | Size get preferredSize => const Size.fromHeight(40); 51 | } 52 | -------------------------------------------------------------------------------- /lib/domain/home/series/serie/serie.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'serie.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Serie _$_$_SerieFromJson(Map json) { 10 | return _$_Serie( 11 | firstAirDate: json['firstAirDate'] == null 12 | ? null 13 | : DateTime.parse(json['firstAirDate'] as String), 14 | genres: (json['genres'] as List) 15 | ?.map( 16 | (e) => e == null ? null : Genre.fromJson(e as Map)) 17 | ?.toList(), 18 | homepage: json['homepage'] as String, 19 | id: json['id'] as int, 20 | name: json['name'] as String, 21 | numberOfEpisodes: json['numberOfEpisodes'] as int, 22 | numberOfSeasons: json['numberOfSeasons'] as int, 23 | overview: json['overview'] as String, 24 | posterPath: json['posterPath'] as String, 25 | type: json['type'] as String, 26 | voteAverage: (json['voteAverage'] as num)?.toDouble(), 27 | contentRatings: json['contentRatings'] == null 28 | ? null 29 | : ContentRatings.fromJson( 30 | json['contentRatings'] as Map), 31 | videos: json['videos'] == null 32 | ? null 33 | : Videos.fromJson(json['videos'] as Map), 34 | ); 35 | } 36 | 37 | Map _$_$_SerieToJson(_$_Serie instance) => { 38 | 'firstAirDate': instance.firstAirDate?.toIso8601String(), 39 | 'genres': instance.genres?.map((e) => e?.toJson())?.toList(), 40 | 'homepage': instance.homepage, 41 | 'id': instance.id, 42 | 'name': instance.name, 43 | 'numberOfEpisodes': instance.numberOfEpisodes, 44 | 'numberOfSeasons': instance.numberOfSeasons, 45 | 'overview': instance.overview, 46 | 'posterPath': instance.posterPath, 47 | 'type': instance.type, 48 | 'voteAverage': instance.voteAverage, 49 | 'contentRatings': instance.contentRatings?.toJson(), 50 | 'videos': instance.videos?.toJson(), 51 | }; 52 | -------------------------------------------------------------------------------- /lib/presentation/search/widgets/search_movies.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../application/home/movies/movies/movies_bloc.dart'; 5 | import '../../../domain/search/search.dart'; 6 | import '../../../injection.dart'; 7 | import '../../core/component_widgets/movie_loading_wigdet.dart'; 8 | import '../../core/component_widgets/poster_image_widget.dart'; 9 | import '../../home/movies/widgets/build_show_info_modal_bottom_sheet_widget.dart'; 10 | 11 | class SearchMovies extends StatelessWidget { 12 | const SearchMovies({ 13 | Key key, 14 | @required this.movieOrSerie, 15 | }) : super(key: key); 16 | 17 | final Search movieOrSerie; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | final appTextTheme = Theme.of(context).textTheme; 22 | 23 | return BlocProvider( 24 | create: (context) => 25 | getIt()..add(MoviesEvent.movieCalled(movieOrSerie.id)), 26 | child: BlocBuilder( 27 | buildWhen: (previous, current) => previous != current, 28 | builder: (context, state) { 29 | return state.map( 30 | initial: (_) => const MovieLoadingWidget(), 31 | loading: (_) => const MovieLoadingWidget(), 32 | loadSuccess: (_) => null, 33 | loadFailure: (_) => const MovieErrorWidget(), 34 | loadSuccessforMovie: (state) { 35 | final movie = state.movie; 36 | 37 | return GestureDetector( 38 | onTap: () => buildShowInfoModalBottomSheet( 39 | appTextTheme: appTextTheme, 40 | context: context, 41 | movieId: movie.id, 42 | ), 43 | child: Tooltip( 44 | message: movie.title, 45 | child: ClipRRect( 46 | borderRadius: BorderRadius.circular(5.0), 47 | child: PosterImageWidget(movieOrSeries: movie), 48 | ), 49 | ), 50 | ); 51 | }, 52 | ); 53 | }, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/main_layout_appbar_navbar/main_bottom_navigation_bar_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | import '../core/app_colors.dart'; 5 | import '../core/app_localizations.dart'; 6 | import '../core/constants/constants.dart'; 7 | import '../core/constants/language_constants.dart'; 8 | 9 | class MainBottomNavigationBar extends StatelessWidget { 10 | final Function(int) onTapTapped; 11 | final int currentIndex; 12 | 13 | const MainBottomNavigationBar({ 14 | @required this.currentIndex, 15 | @required this.onTapTapped, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final lang = AppLocalizations.of(context); 21 | 22 | final List> navbarItems = [ 23 | {"icon": homeIcon, "label": lang.translate(home)}, 24 | {"icon": searchIcon, "label": lang.translate(search)}, 25 | {"icon": favoriteIcon, "label": lang.translate(favorites)}, 26 | {"icon": friendsIcon, "label": lang.translate(people)}, 27 | ]; 28 | 29 | return BottomNavigationBar( 30 | selectedLabelStyle: const TextStyle( 31 | color: AppColors.white, 32 | fontSize: 11.0, 33 | ), 34 | selectedItemColor: AppColors.white, 35 | unselectedItemColor: AppColors.gray, 36 | unselectedLabelStyle: const TextStyle( 37 | color: AppColors.gray, 38 | fontSize: 11.0, 39 | ), 40 | backgroundColor: AppColors.gray.withOpacity(0.3), 41 | type: BottomNavigationBarType.fixed, 42 | currentIndex: currentIndex, 43 | onTap: onTapTapped, 44 | items: navbarItems.map((item) { 45 | final bool isSelected = navbarItems.indexOf(item) == currentIndex; 46 | 47 | return BottomNavigationBarItem( 48 | icon: Padding( 49 | padding: const EdgeInsets.symmetric(vertical: 5.0), 50 | child: SvgPicture.asset( 51 | item["icon"].toString(), 52 | color: isSelected ? AppColors.white : AppColors.gray, 53 | ), 54 | ), 55 | label: item["label"].toString(), 56 | ); 57 | }).toList(), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/presentation/search/widgets/search_series.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../application/home/series/series/series_bloc.dart'; 5 | import '../../../domain/search/search.dart'; 6 | import '../../../injection.dart'; 7 | import '../../core/component_widgets/movie_loading_wigdet.dart'; 8 | import '../../core/component_widgets/poster_image_widget.dart'; 9 | import '../../home/series/widgets/build_show_series_info_modal_bottom_sheet_widget.dart'; 10 | 11 | class SearchSeries extends StatelessWidget { 12 | const SearchSeries({ 13 | Key key, 14 | @required this.movieOrSerie, 15 | }) : super(key: key); 16 | 17 | final Search movieOrSerie; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | final appTextTheme = Theme.of(context).textTheme; 22 | 23 | return BlocProvider( 24 | create: (context) => 25 | getIt()..add(SeriesEvent.serieCalled(movieOrSerie.id)), 26 | child: BlocBuilder( 27 | buildWhen: (previous, current) => previous != current, 28 | builder: (context, state) { 29 | return state.map( 30 | initial: (_) => const MovieLoadingWidget(), 31 | loading: (_) => const MovieLoadingWidget(), 32 | loadSuccess: (_) => null, 33 | loadFailure: (_) => const MovieErrorWidget(), 34 | loadSuccessforSerie: (state) { 35 | final serie = state.serie; 36 | 37 | return GestureDetector( 38 | onTap: () => buildShowSeriesInfoModalBottomSheet( 39 | appTextTheme: appTextTheme, 40 | context: context, 41 | serieId: serie.id, 42 | ), 43 | child: Tooltip( 44 | message: serie.name, 45 | child: ClipRRect( 46 | borderRadius: BorderRadius.circular(5.0), 47 | child: PosterImageWidget(movieOrSeries: serie), 48 | ), 49 | ), 50 | ); 51 | }, 52 | ); 53 | }, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/home/movies/widgets/little_favorite_sub_data_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | 5 | import '../../../../application/home/movies/favorite_movies/favoritemovies_bloc.dart'; 6 | import '../../../../domain/home/movies/movie/movie.dart'; 7 | import '../../../../injection.dart'; 8 | import '../../../core/app_colors.dart'; 9 | import '../../../core/constants/constants.dart'; 10 | 11 | class LitteFavoriteSubDataIcon extends StatelessWidget { 12 | const LitteFavoriteSubDataIcon({ 13 | Key key, 14 | @required this.movie, 15 | }) : super(key: key); 16 | 17 | final Movie movie; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return SizedBox( 22 | height: 15.0, 23 | child: BlocProvider( 24 | create: (context) => getIt() 25 | ..add(const FavoritemoviesEvent.watchFavorites()), 26 | child: BlocBuilder( 27 | buildWhen: (previous, current) => previous != current, 28 | builder: (context, state) { 29 | return state.maybeMap( 30 | orElse: () => Container(), 31 | loading: (_) => const RawMaterialButton( 32 | onPressed: null, 33 | child: CircularProgressIndicator( 34 | backgroundColor: AppColors.white, 35 | )), 36 | failure: (_) => RawMaterialButton( 37 | onPressed: null, 38 | child: Container(color: AppColors.red), 39 | ), 40 | watchSuccess: (state) { 41 | final isMovieEmpty = state.favoriteMovies 42 | .where((e) => e.favoriteMovieId == movie.id); 43 | 44 | return isMovieEmpty.isNotEmpty 45 | ? SvgPicture.asset( 46 | favoriteFilledIcon, 47 | color: AppColors.white, 48 | ) 49 | : const SizedBox(); 50 | }, 51 | ); 52 | }, 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/home/series/widgets/little_favorite_sub_data_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | 5 | import '../../../../application/home/series/favorite_series/favoriteseries_bloc.dart'; 6 | import '../../../../domain/home/series/serie/serie.dart'; 7 | import '../../../../injection.dart'; 8 | import '../../../core/app_colors.dart'; 9 | import '../../../core/constants/constants.dart'; 10 | 11 | class SeriesLitteFavoriteSubDataIcon extends StatelessWidget { 12 | const SeriesLitteFavoriteSubDataIcon({ 13 | Key key, 14 | @required this.serie, 15 | }) : super(key: key); 16 | 17 | final Serie serie; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return SizedBox( 22 | height: 15.0, 23 | child: BlocProvider( 24 | create: (context) => getIt() 25 | ..add(const FavoriteseriesEvent.watchFavorites()), 26 | child: BlocBuilder( 27 | buildWhen: (previous, current) => previous != current, 28 | builder: (context, state) { 29 | return state.maybeMap( 30 | orElse: () => Container(), 31 | loading: (_) => const RawMaterialButton( 32 | onPressed: null, 33 | child: CircularProgressIndicator( 34 | backgroundColor: AppColors.white, 35 | )), 36 | failure: (_) => RawMaterialButton( 37 | onPressed: null, 38 | child: Container(color: AppColors.red), 39 | ), 40 | watchSuccess: (state) { 41 | final isMovieEmpty = state.favoriteSeries 42 | .where((e) => e.favoriteSerieId == serie.id); 43 | 44 | return isMovieEmpty.isNotEmpty 45 | ? SvgPicture.asset( 46 | favoriteFilledIcon, 47 | color: AppColors.white, 48 | ) 49 | : const SizedBox(); 50 | }, 51 | ); 52 | }, 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/presentation/home/series/serie_info/widgets/sub_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../domain/home/series/serie/serie.dart'; 4 | import '../../../../core/component_widgets/age_restriction_widget.dart'; 5 | import '../../widgets/little_favorite_sub_data_icon.dart'; 6 | 7 | class SubData extends StatelessWidget { 8 | final Serie serie; 9 | final TextTheme appTextTheme; 10 | 11 | const SubData({ 12 | Key key, 13 | @required this.appTextTheme, 14 | @required this.serie, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final releaseDates = 20 | serie.contentRatings.results.where((e) => e.iso31661 == "US").isEmpty 21 | ? null 22 | : serie.contentRatings.results 23 | .where((e) => e.iso31661 == "US") 24 | .first 25 | .rating; 26 | 27 | return Row( 28 | mainAxisAlignment: MainAxisAlignment.center, 29 | children: [ 30 | SeriesLitteFavoriteSubDataIcon(serie: serie), 31 | const SizedBox(width: 10.0), 32 | Text(serie.voteAverage.toString(), 33 | style: TextStyle( 34 | fontFamily: appTextTheme.subtitle2.fontFamily, 35 | fontWeight: FontWeight.bold, 36 | color: Colors.green, 37 | )), 38 | const SizedBox(width: 20.0), 39 | Text(serie.firstAirDate.year.toString(), 40 | style: TextStyle( 41 | fontFamily: appTextTheme.subtitle1.fontFamily, 42 | fontWeight: FontWeight.w600, 43 | color: appTextTheme.subtitle1.color, 44 | )), 45 | const SizedBox(width: 20.0), 46 | AgeRestrictionWidget( 47 | age: 48 | releaseDates == null || releaseDates == "" ? "N/A" : releaseDates, 49 | ), 50 | const SizedBox(width: 20.0), 51 | Text("${serie.numberOfSeasons.toString()} Seasons", 52 | style: TextStyle( 53 | fontFamily: appTextTheme.subtitle1.fontFamily, 54 | fontWeight: FontWeight.w600, 55 | color: appTextTheme.subtitle1.color, 56 | )), 57 | ], 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/presentation/core/app_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | 7 | import '../../application/auth/auth_bloc.dart'; 8 | import '../../injection.dart'; 9 | import '../routes/router.gr.dart'; 10 | import 'app_localizations.dart'; 11 | import 'constants/constants.dart'; 12 | import 'theme.dart'; 13 | 14 | class App extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | SystemChrome.setPreferredOrientations([ 18 | DeviceOrientation.portraitUp, 19 | DeviceOrientation.portraitDown, 20 | ]); 21 | 22 | return MultiBlocProvider( 23 | providers: [ 24 | BlocProvider( 25 | create: (context) => 26 | getIt()..add(const AuthEvent.authCheckRequested()), 27 | ), 28 | ], 29 | child: MaterialApp( 30 | title: appName, 31 | debugShowCheckedModeBanner: false, 32 | theme: appTheme(), 33 | builder: ExtendedNavigator.builder( 34 | router: AppRouter(), 35 | ), 36 | supportedLocales: const [ 37 | Locale('en', 'US'), 38 | Locale('pt', 'BR'), 39 | ], 40 | localizationsDelegates: const [ 41 | AppLocalizations.delegate, 42 | GlobalMaterialLocalizations.delegate, 43 | GlobalCupertinoLocalizations.delegate, 44 | GlobalWidgetsLocalizations.delegate, 45 | ], 46 | localeResolutionCallback: (locale, supportedLocales) { 47 | //* Check if the device current locale is supported 48 | for (final supportedLocale in supportedLocales) { 49 | if (supportedLocale.languageCode == locale.languageCode && 50 | supportedLocale.countryCode == locale.countryCode) { 51 | return supportedLocale; 52 | } 53 | } 54 | //* If the device current local isn't supported, use the first one 55 | //* from the list 56 | return supportedLocales.first; 57 | }, 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: movingPictures 2 | description: Real-world movie database mobile application with the Flutter SDK 3 | and DDD clean architecture. 4 | 5 | 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | version: 1.0.0+1 9 | 10 | environment: 11 | sdk: ">=2.7.0 <3.0.0" 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | flutter_localizations: 17 | sdk: flutter 18 | auto_route: ^0.6.9 19 | bloc: ^6.1.0 20 | cloud_firestore: ^0.14.3 21 | connectivity: ^2.0.2 22 | cupertino_icons: ^1.0.0 23 | dartz: ^0.9.2 24 | dio: ^3.0.10 25 | equatable: ^1.2.5 26 | firebase_auth: ^0.18.3 27 | firebase_core: ^0.5.2 28 | flushbar: ^1.10.4 29 | flutter_bloc: ^6.1.1 30 | flutter_hooks: ^0.15.0 31 | flutter_svg: ^0.19.1 32 | freezed_annotation: 0.12.0 33 | get_it: ^5.0.1 34 | google_sign_in: ^4.5.6 35 | injectable: ^1.0.5 36 | json_annotation: ^3.1.0 37 | kt_dart: ^0.8.0 38 | shimmer: ^1.1.2 39 | url_launcher: ^5.7.10 40 | uuid: ^2.2.2 41 | wc_flutter_share: ^0.2.2 42 | 43 | dev_dependencies: 44 | flutter_test: 45 | sdk: flutter 46 | analyzer: null 47 | auto_route_generator: ^0.6.10 48 | build_runner: 1.10.4 49 | flutter_launcher_icons: ^0.8.1 50 | freezed: 0.12.2 51 | injectable_generator: ^1.0.6 52 | json_serializable: ^3.5.0 53 | lint: ^1.3.0 54 | 55 | flutter_icons: 56 | android: true 57 | ios: true 58 | image_path: "assets/logos/moving_pictures_logo_man.png" 59 | 60 | # For information on the generic Dart part of this file, see the 61 | # following page: https://dart.dev/tools/pub/pubspec 62 | # The following section is specific to Flutter. 63 | flutter: 64 | uses-material-design: true 65 | 66 | assets: 67 | - assets/logos/ 68 | - assets/images/ 69 | - assets/icons/ 70 | - assets/icons/regularIcons/ 71 | - assets/icons/numberIcons/ 72 | - lang/ 73 | 74 | fonts: 75 | - family: Montez 76 | fonts: 77 | - asset: fonts/Montez/Montez-Regular.ttf 78 | weight: 500 79 | - family: Roboto 80 | fonts: 81 | - asset: fonts/Roboto/Roboto-Light.ttf 82 | - asset: fonts/Roboto/Roboto-Regular.ttf 83 | - asset: fonts/Roboto/Roboto-Medium.ttf 84 | weight: 500 85 | -------------------------------------------------------------------------------- /lib/presentation/home/movies/movie_info/widgets/sub_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../domain/home/movies/movie/movie.dart'; 4 | import '../../../../core/component_widgets/age_restriction_widget.dart'; 5 | import '../../widgets/little_favorite_sub_data_icon.dart'; 6 | 7 | class SubData extends StatelessWidget { 8 | final Movie movie; 9 | final TextTheme appTextTheme; 10 | 11 | const SubData({ 12 | Key key, 13 | @required this.appTextTheme, 14 | @required this.movie, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final releaseDates = 20 | movie.releaseDates.results.where((e) => e.iso31661 == "US").isEmpty 21 | ? null 22 | : movie.releaseDates.results 23 | .where((e) => e.iso31661 == "US") 24 | .first 25 | .releaseDates 26 | .first 27 | .certification; 28 | 29 | return Row( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | LitteFavoriteSubDataIcon(movie: movie), 33 | const SizedBox(width: 10.0), 34 | Text(movie.voteAverage.toString(), 35 | style: TextStyle( 36 | fontFamily: appTextTheme.subtitle2.fontFamily, 37 | fontWeight: FontWeight.bold, 38 | color: Colors.green, 39 | )), 40 | const SizedBox(width: 20.0), 41 | Text(movie.releaseDate, 42 | style: TextStyle( 43 | fontFamily: appTextTheme.subtitle1.fontFamily, 44 | fontWeight: FontWeight.w600, 45 | color: appTextTheme.subtitle1.color, 46 | )), 47 | const SizedBox(width: 20.0), 48 | AgeRestrictionWidget( 49 | age: 50 | releaseDates == null || releaseDates == "" ? "N/A" : releaseDates, 51 | ), 52 | const SizedBox(width: 20.0), 53 | Text("${movie.runtime} mins", 54 | style: TextStyle( 55 | fontFamily: appTextTheme.subtitle1.fontFamily, 56 | fontWeight: FontWeight.w600, 57 | color: appTextTheme.subtitle1.color, 58 | )), 59 | ], 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/presentation/profile/widgets/tmdb_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:url_launcher/url_launcher.dart'; 3 | 4 | import '../../core/app_localizations.dart'; 5 | import '../../core/component_widgets/flushbar_method.dart'; 6 | import '../../core/constants/constants.dart'; 7 | import '../../core/constants/language_constants.dart'; 8 | 9 | class TmdbBlock extends StatelessWidget { 10 | const TmdbBlock({ 11 | Key key, 12 | @required this.appTextTheme, 13 | }) : super(key: key); 14 | 15 | final TextTheme appTextTheme; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final lang = AppLocalizations.of(context); 20 | 21 | // ignore: avoid_void_async 22 | void _launchURL() async { 23 | const url = tmdbLink; 24 | if (await canLaunch(url)) { 25 | await launch(url); 26 | } else { 27 | showFlushbar( 28 | context: context, message: lang.translate(unexpectedError)); 29 | } 30 | } 31 | 32 | return Center( 33 | child: Column( 34 | children: [ 35 | Text( 36 | lang.translate(notEndorsedByTMDB), 37 | style: appTextTheme.headline6, 38 | textAlign: TextAlign.center, 39 | ), 40 | GestureDetector( 41 | onTap: _launchURL, 42 | child: Column( 43 | children: [ 44 | Padding( 45 | padding: const EdgeInsets.all(10.0), 46 | child: Image.asset( 47 | tmdbLogo, 48 | width: 150.0, 49 | ), 50 | ), 51 | Row( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | lang.translate(goToTMDB), 56 | style: const TextStyle(color: Colors.blue), 57 | ), 58 | const Icon( 59 | Icons.arrow_forward_ios, 60 | color: Colors.blue, 61 | size: 14.0, 62 | ) 63 | ], 64 | ), 65 | ], 66 | ), 67 | ), 68 | ], 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/presentation/main_layout_appbar_navbar/main_body_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../application/auth/auth_bloc.dart'; 6 | import '../../application/auth/user_profile/user_profile_bloc.dart'; 7 | import '../../injection.dart'; 8 | import '../favorites/favorites.dart'; 9 | import '../home/home.dart'; 10 | import '../people/people_screen.dart'; 11 | import '../routes/router.gr.dart'; 12 | import '../search/search_screen.dart'; 13 | import 'main_bottom_navigation_bar_widget.dart'; 14 | 15 | class MainBodyLayout extends StatefulWidget { 16 | @override 17 | _MainBodyLayoutState createState() => _MainBodyLayoutState(); 18 | } 19 | 20 | class _MainBodyLayoutState extends State { 21 | int _currentIndex = 0; 22 | final _children = [ 23 | const Home(), 24 | SearchScreen(), 25 | const Favorites(automaticallyImplyLeading: false), 26 | const PeopleScreen(automaticallyImplyLeading: false), 27 | ]; 28 | 29 | void onTabTapped(int index) { 30 | setState(() { 31 | _currentIndex = index; 32 | }); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return MultiBlocProvider( 38 | providers: [ 39 | BlocProvider( 40 | create: (context) => getIt() 41 | ..add(const UserProfileEvent.watchProfileStarted()), 42 | ), 43 | ], 44 | child: MultiBlocListener( 45 | listeners: [ 46 | BlocListener( 47 | listener: (context, state) { 48 | state.maybeMap( 49 | unAuthenticated: (_) => ExtendedNavigator.of(context) 50 | ..pushAndRemoveUntil( 51 | Routes.signInScreen, 52 | (route) => false, 53 | ), 54 | orElse: () {}, 55 | ); 56 | }, 57 | ), 58 | ], 59 | child: Scaffold( 60 | body: _children[_currentIndex], 61 | bottomNavigationBar: MainBottomNavigationBar( 62 | currentIndex: _currentIndex, 63 | onTapTapped: onTabTapped, 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /assets/icons/numberIcons/7.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/infrastructure/search/search_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../domain/home/movies/movie/movies_failure.dart'; 8 | import '../../domain/search/search.dart'; 9 | import '../../domain/search/search_interface.dart'; 10 | import '../core/credentials.dart'; 11 | 12 | @LazySingleton(as: SearchInterface) 13 | class SearchRepository extends SearchInterface { 14 | final Dio _dio = Dio(); 15 | final String apiKey = TMDB_API_KEY; 16 | final String tmdbUrl = TMDB_URL; 17 | String deviceLocal = Platform.localeName; 18 | 19 | //* Gets only the movie or serie ID, Title and Poster path for trending list 20 | @override 21 | Future>> getTrending() async { 22 | final getTrendingUrl = "$tmdbUrl/trending/all/week"; 23 | final params = { 24 | "api_key": apiKey, 25 | "page": 1, 26 | }; 27 | 28 | try { 29 | final Response> response = await _dio.get( 30 | getTrendingUrl, 31 | queryParameters: params, 32 | ); 33 | final List movieOrSerie = (response.data["results"] as List) 34 | .map((i) => Search.fromJson(i as Map)) 35 | .toList(); 36 | 37 | return right(movieOrSerie); 38 | } catch (e) { 39 | return left(const MovieFailure.unexpected()); 40 | } 41 | } 42 | 43 | @override 44 | Future>> getSearchQuery( 45 | String query) async { 46 | if (deviceLocal == "pt_BR") deviceLocal = "pt-BR"; 47 | if (deviceLocal == "en_US") deviceLocal = "en-US"; 48 | 49 | final getSearchUrl = "$tmdbUrl/search/multi"; 50 | final params = { 51 | "api_key": apiKey, 52 | "language": deviceLocal, 53 | "page": 1, 54 | "query": query, 55 | }; 56 | 57 | try { 58 | final Response> response = await _dio.get( 59 | getSearchUrl, 60 | queryParameters: params, 61 | ); 62 | final List movieOrSerieorPerson = 63 | (response.data["results"] as List) 64 | .map((i) => Search.fromJson(i as Map)) 65 | .toList(); 66 | 67 | return right(movieOrSerieorPerson); 68 | } catch (e) { 69 | return left(const MovieFailure.unexpected()); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/domain/home/movies/movie/movie.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | import '../../shared_classes/genres/genre.dart'; 6 | import '../../shared_classes/videos.dart'; 7 | import 'release_dates.dart'; 8 | 9 | part 'movie.freezed.dart'; 10 | part 'movie.g.dart'; 11 | 12 | @freezed 13 | abstract class Movie implements _$Movie { 14 | const factory Movie({ 15 | @required int id, 16 | @required String title, 17 | @required String posterPath, 18 | @required String overview, 19 | @required String releaseDate, 20 | @required int runtime, 21 | @required ReleaseDates releaseDates, 22 | @required double voteAverage, 23 | @required List genres, 24 | @required String homepage, 25 | @required Videos video, 26 | }) = _Movie; 27 | 28 | const Movie._() : super(); 29 | 30 | factory Movie.fromJson(Map json) => Movie( 31 | id: json['id'] as int ?? -0, 32 | title: json['title'] as String ?? "", 33 | posterPath: json['poster_path'] as String ?? "", 34 | overview: json['overview'] as String ?? "", 35 | releaseDate: json['release_date'] as String ?? "", 36 | runtime: json['runtime'] as int ?? 0, 37 | voteAverage: (json['vote_average'] as num)?.toDouble() ?? 0.0, 38 | homepage: json['homepage'] as String ?? "n/a", 39 | genres: (json['genres'] as List ?? 40 | [ 41 | {"id": -0, "name": "n/a"} 42 | ]) 43 | .map((genre) => Genre.fromJson(genre as Map)) 44 | .toList(), 45 | releaseDates: ReleaseDates.fromJson( 46 | json["release_dates"] as Map ?? 47 | {"release_dates": []}), 48 | video: 49 | Videos.fromJson(json["videos"] as Map ?? {"": ""}), 50 | ); 51 | 52 | @override 53 | Map toJson() => { 54 | "id": id, 55 | "title": title, 56 | "poster_path": posterPath, 57 | "overview": overview, 58 | "runtime": runtime, 59 | "release_date": releaseDate, 60 | "release_dates": releaseDates, 61 | "homepage": homepage, 62 | "vote_average": voteAverage, 63 | "genres": genres, 64 | "videos": video, 65 | }; 66 | 67 | factory Movie.fromFirebase(DocumentSnapshot doc) => 68 | Movie.fromJson(doc.data()); 69 | } 70 | -------------------------------------------------------------------------------- /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: 'com.google.gms.google-services' 26 | apply plugin: 'kotlin-android' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | def keystoreProperties = new Properties() 30 | def keystorePropertiesFile = rootProject.file('key.properties') 31 | if (keystorePropertiesFile.exists()) { 32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 33 | } 34 | 35 | android { 36 | compileSdkVersion 29 37 | 38 | sourceSets { 39 | main.java.srcDirs += 'src/main/kotlin' 40 | } 41 | 42 | lintOptions { 43 | disable 'InvalidPackage' 44 | } 45 | 46 | defaultConfig { 47 | applicationId "com.nifemi.movingPictures" 48 | minSdkVersion 21 49 | targetSdkVersion 29 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | signingConfigs { 55 | release { 56 | keyAlias keystoreProperties['keyAlias'] 57 | keyPassword keystoreProperties['keyPassword'] 58 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 59 | storePassword keystoreProperties['storePassword'] 60 | } 61 | } 62 | buildTypes { 63 | release { 64 | signingConfig signingConfigs.release 65 | } 66 | } 67 | } 68 | 69 | flutter { 70 | source '../..' 71 | } 72 | 73 | dependencies { 74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 75 | implementation platform('com.google.firebase:firebase-bom:26.1.0') 76 | implementation 'com.google.firebase:firebase-analytics:18.0.0' 77 | } 78 | -------------------------------------------------------------------------------- /lib/presentation/profile/widgets/profile_info_block_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | import '../../../domain/auth/app_user.dart'; 5 | import '../../core/app_colors.dart'; 6 | import '../../core/app_localizations.dart'; 7 | import '../../core/constants/constants.dart'; 8 | import '../../core/constants/language_constants.dart'; 9 | 10 | class ProfileInfoBlock extends StatelessWidget { 11 | const ProfileInfoBlock({ 12 | Key key, 13 | @required this.user, 14 | @required this.appTextTheme, 15 | }) : super(key: key); 16 | 17 | final AppUser user; 18 | final TextTheme appTextTheme; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final lang = AppLocalizations.of(context); 23 | 24 | return SizedBox( 25 | child: Row( 26 | children: [ 27 | SizedBox( 28 | height: 60.0, 29 | child: ClipRRect( 30 | borderRadius: BorderRadius.circular(100.0), 31 | child: Image.network( 32 | user.photoURL, 33 | fit: BoxFit.fill, 34 | ), 35 | ), 36 | ), 37 | const SizedBox(width: 20.0), 38 | Column( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | Text(user.name, style: appTextTheme.bodyText1), 42 | Text(user.email, style: appTextTheme.headline6), 43 | const SizedBox(height: 8.0), 44 | Container( 45 | padding: 46 | const EdgeInsets.symmetric(horizontal: 5.0, vertical: 4.0), 47 | decoration: BoxDecoration( 48 | color: AppColors.gray, 49 | borderRadius: BorderRadius.circular(10.0), 50 | ), 51 | child: Row( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | lang.translate(signedInWithGoogle), 56 | style: appTextTheme.subtitle2, 57 | ), 58 | SizedBox( 59 | height: 18.0, 60 | child: SvgPicture.asset( 61 | shieldIcon, 62 | color: Colors.green, 63 | ), 64 | ), 65 | ], 66 | ), 67 | ), 68 | ], 69 | ) 70 | ], 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/domain/home/series/serie/serie.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../../shared_classes/genres/genre.dart'; 5 | import '../../shared_classes/videos.dart'; 6 | import 'content_ratings.dart'; 7 | 8 | part 'serie.freezed.dart'; 9 | part 'serie.g.dart'; 10 | 11 | @freezed 12 | abstract class Serie with _$Serie { 13 | const factory Serie({ 14 | @required DateTime firstAirDate, 15 | @required List genres, 16 | @required String homepage, 17 | @required int id, 18 | @required String name, 19 | @required int numberOfEpisodes, 20 | @required int numberOfSeasons, 21 | @required String overview, 22 | @required String posterPath, 23 | @required String type, 24 | @required double voteAverage, 25 | @required ContentRatings contentRatings, 26 | @required Videos videos, 27 | }) = _Serie; 28 | 29 | const Serie._() : super(); 30 | 31 | factory Serie.fromJson(Map json) => Serie( 32 | firstAirDate: DateTime.parse(json["first_air_date"] as String), 33 | genres: List.from(json["genres"] 34 | .map((x) => Genre.fromJson(x as Map)) as Iterable), 35 | homepage: json["homepage"] as String, 36 | id: json["id"] as int, 37 | name: json["name"] as String, 38 | numberOfEpisodes: json["number_of_episodes"] as int, 39 | numberOfSeasons: json["number_of_seasons"] as int, 40 | overview: json["overview"] as String, 41 | posterPath: json["poster_path"] as String, 42 | type: json["type"] as String, 43 | voteAverage: json["vote_average"].toDouble() as double, 44 | contentRatings: ContentRatings.fromJson( 45 | json["content_ratings"] as Map), 46 | videos: Videos.fromJson(json["videos"] as Map), 47 | ); 48 | 49 | @override 50 | Map toJson() => { 51 | "first_air_date": firstAirDate, 52 | "genres": genres, 53 | "homepage": homepage, 54 | "id": id, 55 | "name": name, 56 | "number_of_episodes": numberOfEpisodes, 57 | "number_of_seasons": numberOfSeasons, 58 | "overview": overview, 59 | "poster_path": posterPath, 60 | "type": type, 61 | "vote_average": voteAverage, 62 | "content_ratings": contentRatings.toJson(), 63 | "videos": videos.toJson(), 64 | }; 65 | factory Serie.fromFirebase(DocumentSnapshot doc) => 66 | Serie.fromJson(doc.data()); 67 | } 68 | -------------------------------------------------------------------------------- /lib/presentation/core/constants/constants.dart: -------------------------------------------------------------------------------- 1 | const String appName = 'Moving Pictures'; 2 | 3 | //*LOGOS 4 | const String movingPicturesLogoRed = 5 | 'assets/logos/moving_pictures_logo_red.png'; 6 | const String movingPicturesManLogoRed = 7 | 'assets/logos/moving_pictures_logo_man_boxy.png'; 8 | const String movingPicturesManLogoRedNoBackground = 9 | 'assets/logos/moving_pictures_logo_man_boxy_no_background.png'; 10 | const String tmdbLogo = 'assets/logos/tmdb-logo.png'; 11 | 12 | //*IMAGES 13 | const String signInScreenBackgroundImageJohnWick = 'assets/images/johnwick.png'; 14 | const String tempo = 'assets/images/tempo.jpg'; 15 | const String signInScreenBackgroundImage = 16 | 'assets/images/the_queens_gambit.png'; 17 | const String theQueensGambitPoster = 18 | 'assets/images/the_queens_gambit_movie_poster.jpg'; 19 | 20 | //*ICONS 21 | // Icons made by "https://www.flaticon.com/authors/freepik" 22 | // Icons made by "https://iconscout.com/unicons" 23 | const String googleIcon = 'assets/icons/regularIcons/google_icon.png'; 24 | const String notificationIcon = 'assets/icons/regularIcons/bell.svg'; 25 | const String homeIcon = 'assets/icons/regularIcons/home.svg'; 26 | const String searchIcon = 'assets/icons/regularIcons/search.svg'; 27 | const String favoriteIcon = 'assets/icons/regularIcons/heart.svg'; 28 | const String favoriteFilledIcon = 'assets/icons/regularIcons/heart_filled.svg'; 29 | const String friendsIcon = 'assets/icons/regularIcons/friends.svg'; 30 | const String cancelIcon = 'assets/icons/regularIcons/cancel.svg'; 31 | const String shareIcon = 'assets/icons/regularIcons/share.svg'; 32 | const String infoIcon = 'assets/icons/regularIcons/info.svg'; 33 | const String shieldIcon = 'assets/icons/regularIcons/shield.svg'; 34 | const String playIcon = 'assets/icons/regularIcons/play.svg'; 35 | const String githubIcon = 'assets/icons/regularIcons/github.svg'; 36 | 37 | //*Top ten number icons 38 | const String number0Icon = 'assets/icons/numberIcons/0.svg'; 39 | const String number1Icon = 'assets/icons/numberIcons/1.svg'; 40 | const String number2Icon = 'assets/icons/numberIcons/2.svg'; 41 | const String number3Icon = 'assets/icons/numberIcons/3.svg'; 42 | const String number4Icon = 'assets/icons/numberIcons/4.svg'; 43 | const String number5Icon = 'assets/icons/numberIcons/5.svg'; 44 | const String number6Icon = 'assets/icons/numberIcons/6.svg'; 45 | const String number7Icon = 'assets/icons/numberIcons/7.svg'; 46 | const String number8Icon = 'assets/icons/numberIcons/8.svg'; 47 | const String number9Icon = 'assets/icons/numberIcons/9.svg'; 48 | 49 | //*Links 50 | const String githubLink = 'https://github.com/thenifemi/movingPictures'; 51 | const String tmdbLink = 'https://developers.themoviedb.org/'; 52 | -------------------------------------------------------------------------------- /lib/application/home/series/favorite_series/favoriteseries_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | import 'package:injectable/injectable.dart'; 7 | 8 | import '../../../../domain/home/series/favorite_serie/favorite_serie.dart'; 9 | import '../../../../domain/home/series/favorite_series_interface.dart'; 10 | import '../../../../domain/home/series/serie/serie_failure.dart'; 11 | 12 | part 'favoriteseries_bloc.freezed.dart'; 13 | part 'favoriteseries_event.dart'; 14 | part 'favoriteseries_state.dart'; 15 | 16 | @injectable 17 | class FavoriteseriesBloc 18 | extends Bloc { 19 | final FavoriteSeriesInterface favoriteSeriesInterface; 20 | StreamSubscription>> 21 | favoriteSeriesStream; 22 | 23 | FavoriteseriesBloc(this.favoriteSeriesInterface) : super(const _Initial()); 24 | 25 | @override 26 | Stream mapEventToState( 27 | FavoriteseriesEvent event, 28 | ) async* { 29 | yield* event.map( 30 | favoriteCreated: (e) async* { 31 | yield const FavoriteseriesState.loading(); 32 | final failureOrSuccess = 33 | await favoriteSeriesInterface.createFavoriteSerie(e.serieId); 34 | 35 | yield failureOrSuccess.fold( 36 | (f) => FavoriteseriesState.failure(f), 37 | (_) => const FavoriteseriesState.createSuccess(), 38 | ); 39 | }, 40 | favoriteDeleted: (e) async* { 41 | yield const FavoriteseriesState.loading(); 42 | final failureOrSuccess = 43 | await favoriteSeriesInterface.deleteFavoriteSerie(e.serieId); 44 | 45 | yield failureOrSuccess.fold( 46 | (f) => FavoriteseriesState.failure(f), 47 | (_) => const FavoriteseriesState.deleteSuccess(), 48 | ); 49 | }, 50 | 51 | //? Stream watchers 52 | watchFavorites: (e) async* { 53 | yield const FavoriteseriesState.loading(); 54 | await favoriteSeriesStream?.cancel(); 55 | 56 | favoriteSeriesStream = favoriteSeriesInterface 57 | .watchSerieFavorites() 58 | .listen((failureOrMovies) => add( 59 | FavoriteseriesEvent.favoritesRecieved(failureOrMovies), 60 | )); 61 | }, 62 | favoritesRecieved: (e) async* { 63 | yield e.failureOrSeries.fold( 64 | (f) => FavoriteseriesState.failure(f), 65 | (movies) => FavoriteseriesState.watchSuccess(movies), 66 | ); 67 | }, 68 | ); 69 | } 70 | 71 | @override 72 | Future close() async { 73 | await favoriteSeriesStream?.cancel(); 74 | return super.close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/presentation/favorites/favorites.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../core/app_colors.dart'; 4 | import '../core/app_localizations.dart'; 5 | import '../core/component_widgets/cancel_button_widget.dart'; 6 | import '../core/constants/language_constants.dart'; 7 | import 'favorite_movies_tab.dart'; 8 | import 'favorite_series_tab.dart'; 9 | 10 | class Favorites extends StatelessWidget { 11 | final bool automaticallyImplyLeading; 12 | final TabController tabController; 13 | 14 | const Favorites({ 15 | Key key, 16 | @required this.automaticallyImplyLeading, 17 | this.tabController, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final lang = AppLocalizations.of(context); 23 | final appTextTheme = Theme.of(context).textTheme; 24 | 25 | return DefaultTabController( 26 | length: 2, 27 | child: Scaffold( 28 | appBar: AppBar( 29 | automaticallyImplyLeading: automaticallyImplyLeading, 30 | leading: automaticallyImplyLeading ? const CancelButton() : null, 31 | title: Text(lang.translate(favorites), style: appTextTheme.headline5), 32 | bottom: FavoritesTabBar(tabController: tabController), 33 | ), 34 | body: TabBarView( 35 | controller: tabController, 36 | children: const [FavoriteMoviesTab(), FavoriteSeriesTab()], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | 43 | //*AppBar Widget 44 | 45 | class FavoritesTabBar extends StatelessWidget implements PreferredSizeWidget { 46 | const FavoritesTabBar({ 47 | Key key, 48 | @required this.tabController, 49 | }) : super(key: key); 50 | 51 | final TabController tabController; 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | final lang = AppLocalizations.of(context); 56 | final appTextTheme = Theme.of(context).textTheme; 57 | return Padding( 58 | padding: const EdgeInsets.only(bottom: 5.0), 59 | child: TabBar( 60 | controller: tabController, 61 | indicatorColor: AppColors.red, 62 | indicatorSize: TabBarIndicatorSize.label, 63 | indicatorWeight: 3.0, 64 | labelColor: appTextTheme.bodyText1.color, 65 | unselectedLabelColor: AppColors.white, 66 | tabs: [ 67 | Padding( 68 | padding: const EdgeInsets.symmetric(vertical: 10.0), 69 | child: Text(lang.translate(movies).toUpperCase()), 70 | ), 71 | Padding( 72 | padding: const EdgeInsets.symmetric(vertical: 10.0), 73 | child: Text(lang.translate(series).toUpperCase()), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | 80 | @override 81 | Size get preferredSize => const Size.fromHeight(40); 82 | } 83 | -------------------------------------------------------------------------------- /lib/application/home/movies/favorite_movies/favoritemovies_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | import 'package:injectable/injectable.dart'; 7 | 8 | import '../../../../domain/home/movies/favorite_movies/favorite_movies.dart'; 9 | import '../../../../domain/home/movies/favorite_movies_interface.dart'; 10 | import '../../../../domain/home/movies/movie/movies_failure.dart'; 11 | 12 | part 'favoritemovies_bloc.freezed.dart'; 13 | part 'favoritemovies_event.dart'; 14 | part 'favoritemovies_state.dart'; 15 | 16 | @injectable 17 | class FavoritemoviesBloc 18 | extends Bloc { 19 | final FavoriteMoviesInterface favoriteMoviesInterface; 20 | StreamSubscription>> 21 | favoriteMoviesStream; 22 | 23 | FavoritemoviesBloc(this.favoriteMoviesInterface) : super(const _Initial()); 24 | 25 | @override 26 | Stream mapEventToState( 27 | FavoritemoviesEvent event, 28 | ) async* { 29 | yield* event.map( 30 | favoriteCreated: (e) async* { 31 | yield const FavoritemoviesState.loading(); 32 | final failureOrSuccess = 33 | await favoriteMoviesInterface.createFavoriteMovie(e.movieId); 34 | 35 | yield failureOrSuccess.fold( 36 | (f) => FavoritemoviesState.failure(f), 37 | (_) => const FavoritemoviesState.createSuccess(), 38 | ); 39 | }, 40 | favoriteDeleted: (e) async* { 41 | yield const FavoritemoviesState.loading(); 42 | final failureOrSuccess = 43 | await favoriteMoviesInterface.deleteFavoriteMovie(e.movieId); 44 | 45 | yield failureOrSuccess.fold( 46 | (f) => FavoritemoviesState.failure(f), 47 | (_) => const FavoritemoviesState.deleteSuccess(), 48 | ); 49 | }, 50 | 51 | //? Stream watchers 52 | watchFavorites: (e) async* { 53 | yield const FavoritemoviesState.loading(); 54 | await favoriteMoviesStream?.cancel(); 55 | 56 | favoriteMoviesStream = favoriteMoviesInterface 57 | .watchMovieFavorites() 58 | .listen((failureOrMovies) => add( 59 | FavoritemoviesEvent.favoritesRecieved(failureOrMovies), 60 | )); 61 | }, 62 | favoritesRecieved: (e) async* { 63 | yield e.failureOrMovies.fold( 64 | (f) => FavoritemoviesState.failure(f), 65 | (movies) => FavoritemoviesState.watchSuccess(movies), 66 | ); 67 | }, 68 | ); 69 | } 70 | 71 | @override 72 | Future close() async { 73 | await favoriteMoviesStream?.cancel(); 74 | return super.close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/application/home/movies/movies/movies_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../../domain/home/movies/movie/movie.dart'; 8 | import '../../../../domain/home/movies/movie/movies_failure.dart'; 9 | import '../../../../domain/home/movies/movie_sub/movie_sub.dart'; 10 | import '../../../../domain/home/movies/movies_interface.dart'; 11 | 12 | part 'movies_bloc.freezed.dart'; 13 | part 'movies_event.dart'; 14 | part 'movies_state.dart'; 15 | 16 | @injectable 17 | class MoviesBloc extends Bloc { 18 | final MoviesInterface moviesInterface; 19 | MoviesBloc(this.moviesInterface) : super(const _Initial()); 20 | 21 | @override 22 | Stream mapEventToState( 23 | MoviesEvent event, 24 | ) async* { 25 | yield* event.map( 26 | movieTypeCalled: (e) async* { 27 | yield const MoviesState.loading(); 28 | final failureOrMovies = 29 | await moviesInterface.getMovieListType(e.movieListType); 30 | 31 | yield failureOrMovies.fold( 32 | (f) => MoviesState.loadFailure(f), 33 | (movies) => MoviesState.loadSuccess(movies), 34 | ); 35 | }, 36 | movieByGenreCalled: (e) async* { 37 | yield const MoviesState.loading(); 38 | final failureOrMovies = 39 | await moviesInterface.getMovieByGenre(e.movieGenreId); 40 | 41 | yield failureOrMovies.fold( 42 | (f) => MoviesState.loadFailure(f), 43 | (movies) => MoviesState.loadSuccess(movies), 44 | ); 45 | }, 46 | similarMoviesCalled: (e) async* { 47 | yield const MoviesState.loading(); 48 | final failureOrMovies = 49 | await moviesInterface.getSimilarMovies(e.movieId); 50 | 51 | yield failureOrMovies.fold( 52 | (f) => MoviesState.loadFailure(f), 53 | (movies) => MoviesState.loadSuccess(movies), 54 | ); 55 | }, 56 | movieCalled: (e) async* { 57 | yield const MoviesState.loading(); 58 | final failureOrMovies = await moviesInterface.getMovie(e.movieId); 59 | 60 | yield failureOrMovies.fold( 61 | (f) => MoviesState.loadFailure(f), 62 | (movie) => MoviesState.loadSuccessforMovie(movie), 63 | ); 64 | }, 65 | movieByCastIdCalled: (e) async* { 66 | yield const MoviesState.loading(); 67 | final failureOrMovies = 68 | await moviesInterface.getMovieByCastId(e.castId); 69 | 70 | yield failureOrMovies.fold( 71 | (f) => MoviesState.loadFailure(f), 72 | (movies) => MoviesState.loadSuccess(movies), 73 | ); 74 | }, 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/application/home/series/series/series_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | import '../../../../domain/home/series/serie/serie.dart'; 8 | import '../../../../domain/home/series/serie/serie_failure.dart'; 9 | import '../../../../domain/home/series/serie_sub/serie_sub.dart'; 10 | import '../../../../domain/home/series/series_interface.dart'; 11 | 12 | part 'series_bloc.freezed.dart'; 13 | part 'series_event.dart'; 14 | part 'series_state.dart'; 15 | 16 | @injectable 17 | class SeriesBloc extends Bloc { 18 | final SeriesInterface seriesInterface; 19 | SeriesBloc(this.seriesInterface) : super(const _Initial()); 20 | 21 | @override 22 | Stream mapEventToState( 23 | SeriesEvent event, 24 | ) async* { 25 | yield* event.map( 26 | serieTypeCalled: (e) async* { 27 | yield const SeriesState.loading(); 28 | final failureOrMovies = 29 | await seriesInterface.getSerieListType(e.serieListType); 30 | 31 | yield failureOrMovies.fold( 32 | (f) => SeriesState.loadFailure(f), 33 | (series) => SeriesState.loadSuccess(series), 34 | ); 35 | }, 36 | serieByGenreCalled: (e) async* { 37 | yield const SeriesState.loading(); 38 | final failureOrMovies = 39 | await seriesInterface.getSerieByGenre(e.serieGenreId); 40 | 41 | yield failureOrMovies.fold( 42 | (f) => SeriesState.loadFailure(f), 43 | (series) => SeriesState.loadSuccess(series), 44 | ); 45 | }, 46 | similarSeriesCalled: (e) async* { 47 | yield const SeriesState.loading(); 48 | final failureOrMovies = 49 | await seriesInterface.getSimilarSeries(e.serieId); 50 | 51 | yield failureOrMovies.fold( 52 | (f) => SeriesState.loadFailure(f), 53 | (series) => SeriesState.loadSuccess(series), 54 | ); 55 | }, 56 | serieCalled: (e) async* { 57 | yield const SeriesState.loading(); 58 | final failureOrMovies = await seriesInterface.getSerie(e.serieId); 59 | 60 | yield failureOrMovies.fold( 61 | (f) => SeriesState.loadFailure(f), 62 | (series) => SeriesState.loadSuccessforSerie(series), 63 | ); 64 | }, 65 | serieByCastIdCalled: (e) async* { 66 | yield const SeriesState.loading(); 67 | final failureOrMovies = 68 | await seriesInterface.getSerieByCastId(e.castId); 69 | 70 | yield failureOrMovies.fold( 71 | (f) => SeriesState.loadFailure(f), 72 | (series) => SeriesState.loadSuccess(series), 73 | ); 74 | }, 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/infrastructure/home/series/favorite_series_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:injectable/injectable.dart'; 4 | 5 | import '../../../domain/home/series/favorite_serie/favorite_serie.dart'; 6 | import '../../../domain/home/series/favorite_series_interface.dart'; 7 | import '../../../domain/home/series/serie/serie_failure.dart'; 8 | import '../../../infrastructure/core/firestore_helper.dart'; 9 | 10 | @LazySingleton(as: FavoriteSeriesInterface) 11 | class FavoriteSeriesRepository extends FavoriteSeriesInterface { 12 | final FirebaseFirestore _firestore; 13 | 14 | FavoriteSeriesRepository(this._firestore); 15 | 16 | @override 17 | Future> createFavoriteSerie(int serieId) async { 18 | try { 19 | final favoriteSerieDoc = await _firestore.favoriteSerieDocument(serieId); 20 | await favoriteSerieDoc.set({"id": serieId}); 21 | 22 | return right(unit); 23 | } on FirebaseException catch (e) { 24 | if (e.message.contains('PERMISSION_DENIED')) { 25 | return left(const SerieFailure.insufficientPermissions()); 26 | } else { 27 | //log error 28 | return left(const SerieFailure.unexpected()); 29 | } 30 | } 31 | } 32 | 33 | @override 34 | Future> deleteFavoriteSerie(int serieId) async { 35 | try { 36 | final favoriteSerieDoc = await _firestore.favoriteSerieDocument(serieId); 37 | await favoriteSerieDoc.delete(); 38 | 39 | return right(unit); 40 | } on FirebaseException catch (e) { 41 | if (e is FirebaseException && e.message.contains('PERMISSION_DENIED')) { 42 | return left(const SerieFailure.insufficientPermissions()); 43 | } else if (e.message.contains('NOT_FOUND')) { 44 | return left(const SerieFailure.unexpected()); 45 | } else { 46 | //log error 47 | return left(const SerieFailure.unexpected()); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | Stream>> 54 | watchSerieFavorites() async* { 55 | final userDoc = await _firestore.userDocument(); 56 | yield* userDoc 57 | .collection('series') 58 | .snapshots() 59 | .map( 60 | (snapshot) => right>( 61 | snapshot.docs 62 | .map((doc) => FavoriteSerie.fromFirebase(doc)) 63 | .toList(), 64 | ), 65 | ) 66 | .handleError((e) { 67 | if (e is FirebaseException && e.message.contains('PERMISSION_DENIED')) { 68 | return left(const SerieFailure.insufficientPermissions()); 69 | } else { 70 | return left(const SerieFailure.unexpected()); 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/infrastructure/home/movies/favorite_movies_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:injectable/injectable.dart'; 4 | 5 | import '../../../domain/home/movies/favorite_movies/favorite_movies.dart'; 6 | import '../../../domain/home/movies/favorite_movies_interface.dart'; 7 | import '../../../domain/home/movies/movie/movies_failure.dart'; 8 | import '../../../infrastructure/core/firestore_helper.dart'; 9 | 10 | @LazySingleton(as: FavoriteMoviesInterface) 11 | class FavoriteMoviesRepository extends FavoriteMoviesInterface { 12 | final FirebaseFirestore _firestore; 13 | 14 | FavoriteMoviesRepository(this._firestore); 15 | 16 | @override 17 | Future> createFavoriteMovie(int movieId) async { 18 | try { 19 | final favoriteMovieDoc = await _firestore.favoriteMovieDocument(movieId); 20 | await favoriteMovieDoc.set({"id": movieId}); 21 | 22 | return right(unit); 23 | } on FirebaseException catch (e) { 24 | if (e.message.contains('PERMISSION_DENIED')) { 25 | return left(const MovieFailure.insufficientPermissions()); 26 | } else { 27 | //log error 28 | return left(const MovieFailure.unexpected()); 29 | } 30 | } 31 | } 32 | 33 | @override 34 | Future> deleteFavoriteMovie(int movieId) async { 35 | try { 36 | final favoriteMovieDoc = await _firestore.favoriteMovieDocument(movieId); 37 | await favoriteMovieDoc.delete(); 38 | 39 | return right(unit); 40 | } on FirebaseException catch (e) { 41 | if (e is FirebaseException && e.message.contains('PERMISSION_DENIED')) { 42 | return left(const MovieFailure.insufficientPermissions()); 43 | } else if (e.message.contains('NOT_FOUND')) { 44 | return left(const MovieFailure.unexpected()); 45 | } else { 46 | //log error 47 | return left(const MovieFailure.unexpected()); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | Stream>> 54 | watchMovieFavorites() async* { 55 | final userDoc = await _firestore.userDocument(); 56 | yield* userDoc 57 | .collection('movies') 58 | .snapshots() 59 | .map( 60 | (snapshot) => right>( 61 | snapshot.docs 62 | .map((doc) => FavoriteMovie.fromFirebase(doc)) 63 | .toList(), 64 | ), 65 | ) 66 | .handleError((e) { 67 | if (e is FirebaseException && e.message.contains('PERMISSION_DENIED')) { 68 | return left(const MovieFailure.insufficientPermissions()); 69 | } else { 70 | return left(const MovieFailure.unexpected()); 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/presentation/home/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../core/app_colors.dart'; 4 | import 'movies/movies_tab_screen.dart'; 5 | import 'series/series_tab_screen.dart'; 6 | import 'shared_widgets/home_app_bar.dart'; 7 | 8 | class Home extends StatefulWidget { 9 | final TextTheme appTextTheme; 10 | final TabController tabController; 11 | 12 | const Home({ 13 | Key key, 14 | this.tabController, 15 | this.appTextTheme, 16 | }) : super(key: key); 17 | 18 | @override 19 | _HomeState createState() => _HomeState(); 20 | } 21 | 22 | class _HomeState extends State with TickerProviderStateMixin { 23 | AnimationController _colorAnimationController; 24 | AnimationController _textAnimationController; 25 | Animation _colorTween; 26 | ScrollController scrollController = ScrollController(); 27 | 28 | @override 29 | void initState() { 30 | _colorAnimationController = 31 | AnimationController(vsync: this, duration: const Duration()); 32 | 33 | _colorTween = ColorTween(begin: Colors.transparent, end: AppColors.black) 34 | .animate(_colorAnimationController); 35 | 36 | _textAnimationController = 37 | AnimationController(vsync: this, duration: const Duration()); 38 | 39 | super.initState(); 40 | } 41 | 42 | // ignore: missing_return 43 | bool _scrollListener(ScrollNotification scrollInfo) { 44 | if (scrollInfo.metrics.axis == Axis.vertical) { 45 | _colorAnimationController.animateTo(scrollInfo.metrics.pixels / 350); 46 | 47 | _textAnimationController 48 | .animateTo((scrollInfo.metrics.pixels - 350) / 50); 49 | return true; 50 | } 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return DefaultTabController( 56 | length: 2, 57 | child: Scaffold( 58 | body: NotificationListener( 59 | onNotification: _scrollListener, 60 | child: Stack( 61 | children: [ 62 | TabBarView( 63 | controller: widget.tabController, 64 | physics: const NeverScrollableScrollPhysics(), 65 | children: const [ 66 | MoviesTabScreen(), 67 | SeriesTabScreen(), 68 | ], 69 | ), 70 | SizedBox( 71 | height: MediaQuery.of(context).size.height / 10, 72 | child: AnimatedBuilder( 73 | animation: _colorAnimationController, 74 | builder: (context, child) => HomeAppBar( 75 | tabController: widget.tabController, 76 | colorTween: _colorTween, 77 | ), 78 | ), 79 | ), 80 | ], 81 | ), 82 | ), 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/presentation/search/widgets/search_cast.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../application/home/casts/casts_bloc.dart'; 6 | import '../../../domain/search/search.dart'; 7 | import '../../../infrastructure/core/credentials.dart'; 8 | import '../../../injection.dart'; 9 | import '../../core/component_widgets/movie_loading_wigdet.dart'; 10 | import '../../routes/router.gr.dart'; 11 | 12 | class SearchCast extends StatelessWidget { 13 | const SearchCast({ 14 | Key key, 15 | @required this.movieOrSerie, 16 | }) : super(key: key); 17 | 18 | final Search movieOrSerie; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocProvider( 23 | create: (context) => 24 | getIt()..add(CastsEvent.getCastCalled(movieOrSerie.id)), 25 | child: BlocBuilder( 26 | buildWhen: (previous, current) => previous != current, 27 | builder: (context, state) { 28 | return state.map( 29 | initial: (_) => const MovieLoadingWidget(), 30 | loading: (_) => const MovieLoadingWidget(), 31 | loadSuccess: (state) { 32 | final person = state.cast; 33 | 34 | return GestureDetector( 35 | onTap: () => ExtendedNavigator.of(context) 36 | .pushCastMoviesScreen(cast: person), 37 | child: Tooltip( 38 | message: person.name, 39 | child: ClipRRect( 40 | borderRadius: BorderRadius.circular(5.0), 41 | child: Image.network( 42 | "$MOVIE_POSTER_PATH${person.profilePath}", 43 | fit: BoxFit.cover, 44 | errorBuilder: (context, e, error) => 45 | const Center(child: Icon(Icons.image_outlined)), 46 | loadingBuilder: (context, child, loadingProgress) { 47 | if (loadingProgress == null) return child; 48 | return Center( 49 | child: CircularProgressIndicator( 50 | value: loadingProgress.expectedTotalBytes != null 51 | ? loadingProgress.cumulativeBytesLoaded / 52 | loadingProgress.expectedTotalBytes 53 | : null, 54 | ), 55 | ); 56 | }, 57 | ), 58 | ), 59 | ), 60 | ); 61 | }, 62 | loadFailure: (_) => const MovieErrorWidget(), 63 | ); 64 | }, 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/presentation/home/movies/movie_info/movie_info.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../../../domain/home/movies/movie/movie.dart'; 6 | import '../../../../infrastructure/core/credentials.dart'; 7 | import '../../../core/app_colors.dart'; 8 | import '../../../core/component_widgets/cancel_button_widget.dart'; 9 | import 'widgets/items_widget.dart'; 10 | import 'widgets/more_like_this_block_widget.dart'; 11 | 12 | class MovieInfo extends StatelessWidget { 13 | final Movie movie; 14 | 15 | const MovieInfo({ 16 | Key key, 17 | @required this.movie, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final appTextTheme = Theme.of(context).textTheme; 23 | final heightSize = MediaQuery.of(context).size.height; 24 | 25 | return Scaffold( 26 | body: SingleChildScrollView( 27 | child: Column( 28 | children: [ 29 | SizedBox(height: heightSize * 0.04), 30 | //* Top Container: Holds Movie/Series information 31 | Stack( 32 | children: [ 33 | Container( 34 | decoration: BoxDecoration( 35 | image: DecorationImage( 36 | image: 37 | NetworkImage("$MOVIE_POSTER_PATH${movie.posterPath}"), 38 | fit: BoxFit.cover, 39 | ), 40 | ), 41 | child: BackdropFilter( 42 | filter: ImageFilter.blur(sigmaX: 30.0, sigmaY: 30.0), 43 | child: Container( 44 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 45 | decoration: BoxDecoration( 46 | gradient: LinearGradient( 47 | colors: [ 48 | Colors.transparent, 49 | AppColors.black.withOpacity(0.9) 50 | ], 51 | begin: Alignment.topCenter, 52 | end: Alignment.bottomCenter, 53 | ), 54 | ), 55 | child: Items( 56 | appTextTheme: appTextTheme, 57 | movie: movie, 58 | ), 59 | ), 60 | ), 61 | ), 62 | const Align( 63 | alignment: Alignment.topLeft, 64 | child: CancelButton(), 65 | ), 66 | ], 67 | ), 68 | 69 | //* Bottom Container: Holds [MORE LIKE THIS] 70 | MoreLikeThisBlock( 71 | movie: movie, 72 | ), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/presentation/home/series/serie_info/serie_info.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../../../domain/home/series/serie/serie.dart'; 6 | import '../../../../infrastructure/core/credentials.dart'; 7 | import '../../../core/app_colors.dart'; 8 | import '../../../core/component_widgets/cancel_button_widget.dart'; 9 | import 'widgets/items_widget.dart'; 10 | import 'widgets/more_like_this_block_widget.dart'; 11 | 12 | class SerieInfo extends StatelessWidget { 13 | final Serie serie; 14 | 15 | const SerieInfo({ 16 | Key key, 17 | @required this.serie, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final appTextTheme = Theme.of(context).textTheme; 23 | final heightSize = MediaQuery.of(context).size.height; 24 | 25 | return Scaffold( 26 | body: SingleChildScrollView( 27 | child: Column( 28 | children: [ 29 | SizedBox(height: heightSize * 0.04), 30 | //* Top Container: Holds serie/Series information 31 | Stack( 32 | children: [ 33 | Container( 34 | decoration: BoxDecoration( 35 | image: DecorationImage( 36 | image: 37 | NetworkImage("$MOVIE_POSTER_PATH${serie.posterPath}"), 38 | fit: BoxFit.cover, 39 | ), 40 | ), 41 | child: BackdropFilter( 42 | filter: ImageFilter.blur(sigmaX: 30.0, sigmaY: 30.0), 43 | child: Container( 44 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 45 | decoration: BoxDecoration( 46 | gradient: LinearGradient( 47 | colors: [ 48 | Colors.transparent, 49 | AppColors.black.withOpacity(0.9) 50 | ], 51 | begin: Alignment.topCenter, 52 | end: Alignment.bottomCenter, 53 | ), 54 | ), 55 | child: Items( 56 | appTextTheme: appTextTheme, 57 | serie: serie, 58 | ), 59 | ), 60 | ), 61 | ), 62 | const Align( 63 | alignment: Alignment.topLeft, 64 | child: CancelButton(), 65 | ), 66 | ], 67 | ), 68 | 69 | //* Bottom Container: Holds [MORE LIKE THIS] 70 | MoreLikeThisBlock( 71 | serie: serie, 72 | ), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | --------------------------------------------------------------------------------