├── android ├── settings_aar.gradle ├── app │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── movie_app_bloc_pattern │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── .classpath │ ├── .project │ └── build.gradle ├── gradle.properties ├── 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 │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist └── .gitignore ├── lib ├── src │ ├── utils │ │ ├── readme.md │ │ ├── preferences.dart │ │ └── auth.dart │ ├── configs │ │ ├── font_size_config.dart │ │ ├── configs.dart │ │ ├── color_config.dart │ │ ├── size_config.dart │ │ └── strings.dart │ ├── views │ │ ├── discover │ │ │ └── discover_page.dart │ │ ├── details │ │ │ ├── components │ │ │ │ ├── header_text.dart │ │ │ │ ├── hide_show_btn.dart │ │ │ │ ├── backdrop_card.dart │ │ │ │ ├── genre_component.dart │ │ │ │ ├── sliver_appbar_title.dart │ │ │ │ ├── storyline_text.dart │ │ │ │ ├── sliver_appbar_back_btn.dart │ │ │ │ ├── poster_card.dart │ │ │ │ └── bottom_tabbar.dart │ │ │ ├── episode_details │ │ │ │ └── episode_details_info │ │ │ │ │ └── episode_details_info.dart │ │ │ ├── season_details │ │ │ │ ├── tabs │ │ │ │ │ └── about │ │ │ │ │ │ └── season_about_tab.dart │ │ │ │ └── components │ │ │ │ │ └── season_bottom_tabbar.dart │ │ │ ├── tv_details │ │ │ │ └── tabs │ │ │ │ │ ├── tv_list │ │ │ │ │ ├── tv_list.dart │ │ │ │ │ ├── similar_list.dart │ │ │ │ │ └── recommended_list.dart │ │ │ │ │ ├── about │ │ │ │ │ └── components │ │ │ │ │ │ └── networks.dart │ │ │ │ │ └── reviews │ │ │ │ │ └── reviews_tab.dart │ │ │ ├── movie_deatils │ │ │ │ └── tabs │ │ │ │ │ ├── movie _list │ │ │ │ │ ├── movie_list.dart │ │ │ │ │ ├── similar_list.dart │ │ │ │ │ ├── collections_list.dart │ │ │ │ │ └── recommended_list.dart │ │ │ │ │ └── reviews │ │ │ │ │ └── reviews_tab.dart │ │ │ └── people_details │ │ │ │ ├── tabs │ │ │ │ └── about │ │ │ │ │ └── components │ │ │ │ │ ├── chips_builder.dart │ │ │ │ │ └── about_info.dart │ │ │ │ └── components │ │ │ │ ├── people_flexible_spacebar_option.dart │ │ │ │ └── people_bottom_tabbar.dart │ │ ├── profile │ │ │ ├── profile_page.dart │ │ │ └── components │ │ │ │ └── guest_credentials.dart │ │ ├── home │ │ │ ├── search │ │ │ │ ├── components │ │ │ │ │ ├── empty_search.dart │ │ │ │ │ ├── search_history_list.dart │ │ │ │ │ └── search_bottom_tabbar.dart │ │ │ │ └── tabs │ │ │ │ │ ├── movies_search_list.dart │ │ │ │ │ ├── tv_search_list.dart │ │ │ │ │ └── people_search_list.dart │ │ │ └── components │ │ │ │ └── header_tile.dart │ │ ├── auth │ │ │ └── components │ │ │ │ ├── auth_btn.dart │ │ │ │ └── webview_request_authorization.dart │ │ └── watchlist │ │ │ ├── components │ │ │ └── watchlist_bottom_tabbar.dart │ │ │ ├── tabs │ │ │ ├── tv_watchlist.dart │ │ │ └── movie_watchlist.dart │ │ │ └── watchlist_page.dart │ ├── controllers │ │ ├── base_controller.dart │ │ ├── test_bloc.dart │ │ ├── download_controller.dart │ │ ├── configuration_controller.dart │ │ └── season_controller.dart │ ├── mixins │ │ ├── loading_spinner_mixin.dart │ │ ├── query_parameter_mixin.dart │ │ └── avatar.dart │ ├── middlewares │ │ └── auth_middleware.dart │ ├── global │ │ ├── more_btn.dart │ │ ├── loading_spinner.dart │ │ ├── form_wrapper.dart │ │ ├── external_id_btn.dart │ │ └── add_more_pagination_btn.dart │ ├── models │ │ ├── configurations │ │ │ ├── language_configuration_model.dart │ │ │ ├── country_configuration_model.dart │ │ │ └── api_configuration_model.dart │ │ ├── lists │ │ │ ├── list_model.dart │ │ │ └── movie_list_details.dart │ │ ├── peoples │ │ │ ├── people_external_ids.dart │ │ │ ├── people_model.dart │ │ │ ├── people_movie_credits.dart │ │ │ └── people_tv_credits.dart │ │ ├── account_model.dart │ │ └── results │ │ │ ├── movie_result_model.dart │ │ │ └── tv_result_model.dart │ ├── services │ │ ├── auth_guest_service.dart │ │ ├── configuration_service.dart │ │ ├── search_service.dart │ │ ├── download_service.dart │ │ ├── trending_results_service.dart │ │ ├── people_service.dart │ │ └── results_service.dart │ ├── helpers │ │ └── widget_builder_helper.dart │ ├── skeletons │ │ ├── bottom_nav_skeleton.dart │ │ ├── page_skeleton.dart │ │ └── horizontal_bloc_skeleton.dart │ ├── exceptions │ │ └── app_exceptions.dart │ ├── routes │ │ └── route_const_str.dart │ └── bindings │ │ └── init_bindings.dart ├── service_locator.dart ├── main.dart ├── init_bindings.dart └── youtube_iframe.html ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png └── manifest.json ├── fonts ├── Poppins-Black.ttf ├── Poppins-Bold.ttf ├── Poppins-Light.ttf ├── Poppins-Thin.ttf ├── Poppins-Italic.ttf ├── Poppins-Medium.ttf ├── Poppins-Regular.ttf ├── Poppins-BoldItalic.ttf ├── Poppins-ExtraBold.ttf ├── Poppins-ExtraLight.ttf ├── Poppins-SemiBold.ttf ├── Poppins-ThinItalic.ttf ├── Poppins-BlackItalic.ttf ├── Poppins-LightItalic.ttf ├── Poppins-MediumItalic.ttf ├── Poppins-ExtraBoldItalic.ttf ├── Poppins-SemiBoldItalic.ttf └── Poppins-ExtraLightItalic.ttf ├── .metadata ├── .vscode └── launch.json ├── .gitignore ├── pubspec.yaml ├── assets ├── facebook.svg ├── twitter.svg ├── imdb.svg └── instagram.svg ├── .github └── workflows │ └── dart.yml ├── analysis_options.yaml └── README.md /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 | -------------------------------------------------------------------------------- /lib/src/utils/readme.md: -------------------------------------------------------------------------------- 1 | ### this directory is actually local storage for cache data -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/web/favicon.png -------------------------------------------------------------------------------- /fonts/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Black.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Light.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Thin.ttf -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /fonts/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Italic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /fonts/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /fonts/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /fonts/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /lib/src/configs/font_size_config.dart: -------------------------------------------------------------------------------- 1 | const double l = 24; 2 | const double m = 20; 3 | const double n = 16; 4 | -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /fonts/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /fonts/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /fonts/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/fonts/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /lib/src/configs/configs.dart: -------------------------------------------------------------------------------- 1 | export 'color_config.dart'; 2 | export 'font_size_config.dart'; 3 | export 'size_config.dart'; 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himmat12/movie_db_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/movie_app_bloc_pattern/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.movie_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/views/discover/discover_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DiscoverPage extends StatelessWidget { 4 | const DiscoverPage({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Center( 9 | child: Text('Discover'), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.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: bac5a0db3b52f76201e1892a190cda70c876416f 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /lib/src/controllers/base_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | enum ViewState { idle, retrived, busy, error } 4 | 5 | class BaseController extends GetxController { 6 | final _state = ViewState.idle.obs; 7 | 8 | ViewState get state => _state.value; 9 | 10 | void setState(ViewState newState) { 11 | _state.value = newState; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/src/mixins/loading_spinner_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 2 | 3 | import '../configs/configs.dart'; 4 | 5 | mixin LoadingSpinnerMixin { 6 | final fadingCircleSpinner = const SpinKitFadingCircle( 7 | size: 14, 8 | color: primaryDarkBlue, 9 | ); 10 | final horizontalLoading = const SpinKitThreeBounce( 11 | size: 14, 12 | color: primaryblue, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/configs/color_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | const Color primaryDarkBlue = Color(0xff211b3b); 4 | const Color secondaryDarkBlue = Color(0xff2d2747); 5 | const Color primaryblue = Color(0xff1b99cf); 6 | const Color primaryDark = Color(0xff262829); 7 | const Color primaryWhite = Color(0xfff7f7f7); 8 | const Color primaryDarkBlue05 = Color(0xff313038); 9 | const Color primaryblue05 = Color(0xff1fafed); 10 | -------------------------------------------------------------------------------- /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/src/middlewares/auth_middleware.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get_navigation/src/routes/route_middleware.dart'; 3 | 4 | import '../utils/auth.dart'; 5 | 6 | class AuthMiddleware extends GetMiddleware { 7 | @override 8 | int? get priority => 1; 9 | 10 | @override 11 | RouteSettings? redirect(String? route) => 12 | Auth().isLoggedIn ? const RouteSettings(name: "/dashboard") : null; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/global/more_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MoreBtn extends StatelessWidget { 4 | const MoreBtn({ 5 | Key? key, 6 | this.onTap, 7 | this.title, 8 | }) : super(key: key); 9 | 10 | final String? title; 11 | final void Function()? onTap; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GestureDetector( 16 | onTap: onTap ?? () {}, 17 | child: Container( 18 | color: Colors.transparent, 19 | child: Text(title ?? 'More'), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "movie_app_bloc_pattern", 9 | "request": "launch", 10 | "type": "dart", 11 | "args": [ 12 | "--enable-experiment=non-nullable", 13 | ], 14 | "flutterMode": "debug" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /lib/src/configs/size_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SizeConfig { 4 | late MediaQueryData mediaQueryData; 5 | late double screenHeight; 6 | late double screenWidth; 7 | late double blockVerticalHeight; 8 | late double blockHorizontalWidth; 9 | 10 | void initSize(BuildContext context) { 11 | mediaQueryData = MediaQuery.of(context); 12 | screenHeight = mediaQueryData.size.height; 13 | screenWidth = mediaQueryData.size.width; 14 | blockVerticalHeight = screenHeight / 100; 15 | blockHorizontalWidth = screenWidth / 100; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/global/loading_spinner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 2 | 3 | import '../configs/color_config.dart'; 4 | import '../configs/configs.dart'; 5 | 6 | class LoadingSpinner { 7 | SpinKitFadingCircle fadingCircleSpinner = const SpinKitFadingCircle( 8 | // size: 14, 9 | color: primaryDarkBlue, 10 | ); 11 | 12 | SpinKitFadingCircle miniFadingCircleSpinner = const SpinKitFadingCircle( 13 | size: 22, 14 | color: primaryWhite, 15 | ); 16 | 17 | SpinKitThreeBounce horizontalLoading = const SpinKitThreeBounce( 18 | size: 14, 19 | color: primaryblue, 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movie_app", 3 | "short_name": "movie_app", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /lib/src/views/details/components/header_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../configs/configs.dart'; 4 | 5 | class HeaderBuilder extends StatelessWidget { 6 | const HeaderBuilder({Key? key, this.headerText}) : super(key: key); 7 | final String? headerText; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Text( 12 | headerText ?? 'headerText', 13 | maxLines: 2, 14 | overflow: TextOverflow.ellipsis, 15 | style: TextStyle( 16 | color: primaryDarkBlue.withOpacity(0.8), 17 | fontSize: m - 4, 18 | fontWeight: FontWeight.w700, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.20' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /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/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /lib/src/models/configurations/language_configuration_model.dart: -------------------------------------------------------------------------------- 1 | class LanguageConfigurationModel { 2 | LanguageConfigurationModel({ 3 | required this.iso6391, 4 | required this.englishName, 5 | required this.name, 6 | }); 7 | 8 | String iso6391; 9 | String englishName; 10 | String name; 11 | 12 | factory LanguageConfigurationModel.fromJson(Map json) => 13 | LanguageConfigurationModel( 14 | iso6391: json["iso_639_1"], 15 | englishName: json["english_name"], 16 | name: json["name"], 17 | ); 18 | 19 | Map toJson() => { 20 | "iso_639_1": iso6391, 21 | "english_name": englishName, 22 | "name": name, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/views/profile/profile_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../utils/auth.dart'; 4 | import 'components/guest_credentials.dart'; 5 | import 'components/user_credentials.dart'; 6 | 7 | class ProfilePage extends StatelessWidget { 8 | const ProfilePage({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: const EdgeInsets.fromLTRB(16, 28, 16, 0), 14 | child: Column( 15 | children: [ 16 | // poster & username 17 | Auth().isGuestLoggedIn 18 | ? const GuestUserCredientials() 19 | : const AuthUserCredentials(), 20 | ], 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/models/configurations/country_configuration_model.dart: -------------------------------------------------------------------------------- 1 | class CountryConfigurationModel { 2 | CountryConfigurationModel({ 3 | required this.iso31661, 4 | required this.englishName, 5 | required this.nativeName, 6 | }); 7 | 8 | String iso31661; 9 | String englishName; 10 | String nativeName; 11 | 12 | factory CountryConfigurationModel.fromJson(Map json) => 13 | CountryConfigurationModel( 14 | iso31661: json["iso_3166_1"], 15 | englishName: json["english_name"], 16 | nativeName: json["native_name"], 17 | ); 18 | 19 | Map toJson() => { 20 | "iso_3166_1": iso31661, 21 | "english_name": englishName, 22 | "native_name": nativeName, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/utils/preferences.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_storage/get_storage.dart'; 2 | 3 | final prefs = GetStorage('prefs'); 4 | 5 | class Prefs { 6 | void setMoveiIstodayState(bool data) => prefs.write('movieIsToday', data); 7 | void setTvIstodayState(bool data) => prefs.write('tvIsToday', data); 8 | 9 | void setMovieNowPlayingState(bool data) => 10 | prefs.write('isMovieNowPlaying', data); 11 | void setTvAiringTodayState(bool data) => prefs.write('isTvAiringToday', data); 12 | 13 | bool get isMovieNowPlayingState => prefs.read('isMovieNowPlaying') ?? true; 14 | bool get isTvAiringTodayState => prefs.read('isTvAiringToday') ?? true; 15 | 16 | bool get movieIsTodayState => prefs.read('movieIsToday') ?? true; 17 | bool get tvIsTodayState => prefs.read('tvIsToday') ?? true; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/services/auth_guest_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class AuthGuestService extends BaseService { 8 | // create guest session 9 | Future createGuestSession() async { 10 | try { 11 | await request( 12 | method: Requests.get, 13 | path: '/3/authentication/guest_session/new', 14 | queryParameter: setQueryParameters(), 15 | ); 16 | 17 | return decodeResponse(response); 18 | } on SocketException { 19 | throw FetchDataException('No Internet Connection'); 20 | } on TimeoutException { 21 | throw ServiceNotRespondingException( 22 | 'Service not responding in time please check your Internet Connection'); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | 1621751648172 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/src/models/lists/list_model.dart: -------------------------------------------------------------------------------- 1 | class ListModel { 2 | ListModel({ 3 | this.description, 4 | this.favoriteCount, 5 | this.id, 6 | this.itemCount, 7 | this.iso6391, 8 | this.listType, 9 | this.name, 10 | this.posterPath, 11 | }); 12 | 13 | String? description; 14 | int? favoriteCount; 15 | int? id; 16 | int? itemCount; 17 | String? iso6391; 18 | String? listType; 19 | String? name; 20 | String? posterPath; 21 | 22 | factory ListModel.fromJson(Map json) => ListModel( 23 | description: json["description"], 24 | favoriteCount: json["favorite_count"], 25 | id: json["id"], 26 | itemCount: json["item_count"], 27 | iso6391: json["iso_639_1"], 28 | listType: json["list_type"], 29 | name: json["name"], 30 | posterPath: json["poster_path"], 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/mixins/query_parameter_mixin.dart: -------------------------------------------------------------------------------- 1 | import '../utils/auth.dart'; 2 | 3 | mixin QueryParameterMixin { 4 | String api = "1a5ebef58b08ad825f24591860b26990"; 5 | // String authorization = 6 | // "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxYTVlYmVmNThiMDhhZDgyNWYyNDU5MTg2MGIyNjk5MCIsInN1YiI6IjYwYTM1OTI2NzMxNGExMDA3OGZjZTRkOCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.VJG0GMDEpcYQBtm5VZlCHEmqTY5jH4kfIkYhosKqOA0"; 7 | 8 | late Map queryParma; 9 | 10 | Map setQueryParameters({required Map query}) { 11 | queryParma = {"api_key": api}; 12 | queryParma.addAll(query); 13 | if (Auth().isLoggedIn == true) { 14 | queryParma["session_id"] = Auth().sessionId; 15 | } 16 | if (Auth().isGuestLoggedIn == true) { 17 | queryParma["guest_session_id"] = Auth().guestSessionId; 18 | } 19 | return queryParma; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/services/configuration_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class ConfigurationService extends BaseService { 8 | Future getConfiguration() async { 9 | try { 10 | await request( 11 | method: Requests.get, 12 | path: "/3/configuration", 13 | header: setHeaders(), 14 | queryParameter: setQueryParameters()); 15 | // ignore: avoid_print 16 | // print('CONFIGURATION STATUS => ${response.statusCode}'); 17 | 18 | return decodeResponse(response); 19 | } on SocketException { 20 | throw FetchDataException('No Internet Connection'); 21 | } on TimeoutException { 22 | throw ServiceNotRespondingException( 23 | 'Service not responding in time please check your Internet Connection'); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/services/search_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class SearchService extends BaseService { 8 | // search query 9 | Future search({ 10 | required String query, 11 | required String resultType, 12 | }) async { 13 | try { 14 | await request( 15 | method: Requests.get, 16 | path: '/3/search/$resultType', 17 | queryParameter: setQueryParameters(query: {"query": query})); 18 | // print(decodeResponse(response)['results']); 19 | 20 | return decodeResponse(response)['results']; 21 | } on SocketException { 22 | throw FetchDataException('No Internet Connection'); 23 | } on TimeoutException { 24 | throw ServiceNotRespondingException( 25 | 'Service not responding in time please check your Internet Connection'); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/views/home/search/components/empty_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../configs/configs.dart'; 4 | 5 | class EmptySearch extends StatelessWidget { 6 | const EmptySearch({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | height: 200, 12 | alignment: Alignment.center, 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Icon( 17 | Icons.history, 18 | size: 48, 19 | color: primaryDarkBlue.withOpacity(0.6), 20 | ), 21 | Text( 22 | 'No search history yet', 23 | style: TextStyle( 24 | color: primaryDarkBlue.withOpacity(0.8), 25 | fontSize: n, 26 | ), 27 | ), 28 | ], 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.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 | .fvm/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /lib/src/helpers/widget_builder_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../controllers/base_controller.dart'; 4 | 5 | class WidgetBuilderHelper extends StatelessWidget { 6 | final ViewState state; 7 | final Widget onLoadingBuilder; 8 | final Widget onSuccessBuilder; 9 | final Widget onErrorBuilder; 10 | 11 | const WidgetBuilderHelper({ 12 | required this.state, 13 | required this.onLoadingBuilder, 14 | required this.onSuccessBuilder, 15 | required this.onErrorBuilder, 16 | Key? key, 17 | }) : super(key: key); 18 | @override 19 | Widget build(BuildContext context) { 20 | switch (state) { 21 | case ViewState.busy: 22 | return onLoadingBuilder; 23 | 24 | case ViewState.error: 25 | return onErrorBuilder; 26 | 27 | case ViewState.retrived: 28 | break; 29 | 30 | default: 31 | break; 32 | } 33 | return onSuccessBuilder; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/controllers/test_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | enum EventAction { increment, decrement, reset } 4 | 5 | class TestBloc { 6 | late int count; 7 | final _testBlocStateController = StreamController(); 8 | Stream get testStateStream => 9 | _testBlocStateController.stream.asBroadcastStream(); 10 | 11 | final _testBlocEventController = StreamController(); 12 | StreamSink get testBlocEventSink => 13 | _testBlocEventController.sink; 14 | 15 | TestBloc() { 16 | count = 0; 17 | 18 | Future.delayed(const Duration(seconds: 3)); 19 | 20 | _testBlocEventController.stream.listen((event) { 21 | if (event == EventAction.increment) { 22 | count++; 23 | } else if (event == EventAction.decrement) { 24 | count--; 25 | } else if (event == EventAction.reset) { 26 | count = 0; 27 | } 28 | 29 | _testBlocStateController.sink.add(count); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/skeletons/bottom_nav_skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:shimmer/shimmer.dart'; 4 | 5 | class BottomNavSkeleton extends StatelessWidget { 6 | const BottomNavSkeleton({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Shimmer.fromColors( 11 | baseColor: Colors.black26, 12 | highlightColor: Colors.blueGrey.shade50, 13 | child: Container( 14 | height: 60, 15 | margin: const EdgeInsets.fromLTRB(6, 6, 6, 6), 16 | child: Row( 17 | mainAxisAlignment: MainAxisAlignment.spaceAround, 18 | children: List.generate( 19 | 4, 20 | (index) => Container( 21 | height: 22, 22 | width: MediaQuery.of(Get.context!).size.width / 5, 23 | color: Colors.black26, 24 | ), 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/services/download_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | // 'https://img.youtube.com/vi/gmRKv7n2If8/hqdefault.jpg' //default test img url 8 | class DownloadService extends BaseService { 9 | // download file 10 | Future downloadFile( 11 | {required String url, required String downloadPath}) async { 12 | try { 13 | final _response = await client.get(Uri.parse(url)).then((value) { 14 | File(downloadPath).writeAsBytes(value.bodyBytes).then((value) { 15 | print('DOWNLOAD COMPLETED: $value'); 16 | }); 17 | }); 18 | 19 | return _response; 20 | } on SocketException { 21 | throw FetchDataException('No Internet Connection'); 22 | } on TimeoutException { 23 | throw ServiceNotRespondingException( 24 | 'Service not responding in time please check your Internet Connection'); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | 25 | 1621751648301 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/src/exceptions/app_exceptions.dart: -------------------------------------------------------------------------------- 1 | class AppException implements Exception { 2 | final String? _message; 3 | final String? _prefix; 4 | 5 | AppException(this._message, this._prefix); 6 | 7 | @override 8 | String toString() => '$_prefix$_message'; 9 | } 10 | 11 | class FetchDataException extends AppException { 12 | FetchDataException([String? message]) 13 | : super(message, 'Error During Communication: '); 14 | } 15 | 16 | class BadRequestException extends AppException { 17 | BadRequestException([String? message]) : super(message, 'Invalid Request: '); 18 | } 19 | 20 | class UnauthorizedException extends AppException { 21 | UnauthorizedException([String? message]) : super(message, 'Unauthorized: '); 22 | } 23 | 24 | class InvalidInputException extends AppException { 25 | InvalidInputException([String? message]) : super(message, 'Invalid Input: '); 26 | } 27 | 28 | class ServiceNotRespondingException extends AppException { 29 | ServiceNotRespondingException([String? message]) 30 | : super(message, 'Not Responding: '); 31 | } 32 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/views/details/components/hide_show_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/configs.dart'; 5 | import '../../../controllers/utility_controller.dart'; 6 | 7 | class ToggleHideShowBtn extends GetView { 8 | const ToggleHideShowBtn({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Row( 13 | mainAxisAlignment: MainAxisAlignment.end, 14 | children: [ 15 | GestureDetector( 16 | onTap: () { 17 | controller.toggleHideShowBtn(); 18 | }, 19 | child: Container( 20 | padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), 21 | color: Colors.transparent, 22 | child: Obx(() => Text( 23 | controller.showText.value != true ? 'More' : 'Less', 24 | style: const TextStyle( 25 | color: primaryblue, 26 | fontSize: n - 2, 27 | ), 28 | )), 29 | ), 30 | ), 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/views/details/episode_details/episode_details_info/episode_details_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../controllers/season_controller.dart'; 5 | import '../../components/storyline_text.dart'; 6 | import '../episode_crew/episode_crew_builder.dart'; 7 | import '../guest_casts/episode_guest_cast.dart'; 8 | 9 | class EpisodeDetailsInfo extends StatelessWidget { 10 | EpisodeDetailsInfo({Key? key}) : super(key: key); 11 | 12 | final _seasonController = Get.find(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Column( 17 | crossAxisAlignment: CrossAxisAlignment.start, 18 | children: [ 19 | const SizedBox(height: 18), 20 | StoryLineTextBuilder( 21 | text: _seasonController.episodeModel.value.overview ?? "overview"), 22 | const SizedBox(height: 18), 23 | GuestCasts(), 24 | const SizedBox(height: 18), 25 | EpisodeCrewBuilder( 26 | crews: _seasonController.episodeModel.value.crew ?? []), 27 | const SizedBox(height: 18), 28 | ], 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/global/form_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_form_builder/flutter_form_builder.dart'; 3 | 4 | class FormWrapper extends StatelessWidget { 5 | const FormWrapper({ 6 | required this.formKey, 7 | Key? key, 8 | this.autovalidateMode, 9 | this.initialValue, 10 | this.onChanged, 11 | this.skipDisabled, 12 | this.enabled, 13 | this.formFields, 14 | }) : super(key: key); 15 | 16 | final Key formKey; 17 | final AutovalidateMode? autovalidateMode; 18 | final Map? initialValue; 19 | final void Function()? onChanged; 20 | final bool? skipDisabled; 21 | final bool? enabled; 22 | final List? formFields; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return FormBuilder( 27 | key: formKey, 28 | autovalidateMode: autovalidateMode ?? AutovalidateMode.onUserInteraction, 29 | initialValue: initialValue ?? {}, 30 | onChanged: onChanged, 31 | skipDisabled: skipDisabled ?? false, 32 | enabled: enabled ?? true, 33 | child: Column( 34 | children: formFields ?? [], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/global/external_id_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../configs/configs.dart'; 6 | import '../controllers/utility_controller.dart'; 7 | 8 | class ExternalIdBtn extends StatelessWidget { 9 | ExternalIdBtn({ 10 | required this.targetUrl, 11 | required this.asset, 12 | this.height = 22, 13 | this.width = 22, 14 | this.color, 15 | this.fit = BoxFit.scaleDown, 16 | Key? key, 17 | }) : super(key: key); 18 | 19 | final String targetUrl; 20 | final String asset; 21 | final double height; 22 | final double width; 23 | final BoxFit fit; 24 | final Color? color; 25 | 26 | final _utilityController = Get.find(); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return GestureDetector( 31 | onTap: () { 32 | _utilityController.loadUrl(url: targetUrl); 33 | }, 34 | child: SvgPicture.asset( 35 | 'assets/$asset', 36 | height: height, 37 | width: width, 38 | color: color ?? primaryDarkBlue.withOpacity(0.8), 39 | fit: fit, 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/services/trending_results_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class TrendingResultsService extends BaseService { 8 | // trending results service 9 | Future getTrendingResults({ 10 | required String mediaType, 11 | required String timeWindow, 12 | String page = "", 13 | }) async { 14 | try { 15 | final response = await request( 16 | method: Requests.get, 17 | path: "/3/trending/$mediaType/$timeWindow", 18 | header: setHeaders(), 19 | queryParameter: setQueryParameters(query: {"page": page}), 20 | ); 21 | // ignore: avoid_print 22 | // print('TRENDING $mediaType RESULTS STATUS => ${response.statusCode}'); 23 | // ignore: avoid_print 24 | // print('PAGE => ${decodeResponse(response)['page']}'); 25 | 26 | return decodeResponse(response)['results']; 27 | } on SocketException { 28 | throw FetchDataException('No Internet Connection'); 29 | } on TimeoutException { 30 | throw ServiceNotRespondingException( 31 | 'Service not responding in time please check your Internet Connection'); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/views/details/components/backdrop_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../../../configs/configs.dart'; 6 | 7 | class BackdropCard extends StatelessWidget { 8 | const BackdropCard({required this.imageUrl, Key? key}) : super(key: key); 9 | 10 | final String imageUrl; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return GestureDetector( 15 | onTap: imageUrl.isEmpty 16 | ? null 17 | : () { 18 | Get.toNamed( 19 | '/backdrop_preview', 20 | arguments: {"filePath": imageUrl}, 21 | ); 22 | }, 23 | child: CachedNetworkImage( 24 | fit: BoxFit.cover, 25 | imageUrl: imageUrl, 26 | errorWidget: (context, url, error) => Container( 27 | alignment: Alignment.center, 28 | // width: 94, 29 | // height: 140, 30 | color: Colors.black12, 31 | child: const Icon( 32 | Icons.error_outline, 33 | color: primaryWhite, 34 | size: 34, 35 | ), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: movie_app 2 | description: A new Flutter project. 3 | 4 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependency_overrides: 12 | platform: ^3.1.0 13 | 14 | dependencies: 15 | flutter: 16 | sdk: flutter 17 | cached_network_image: null 18 | cupertino_icons: ^1.0.2 19 | flutter_form_builder: ^6.0.1 20 | flutter_rating_bar: ^4.0.0 21 | flutter_spinkit: ^5.0.0 22 | flutter_svg: ^0.22.0 23 | get: ^4.1.4 24 | get_it: ^7.1.3 25 | get_storage: ^2.0.2 26 | http: ^0.13.3 27 | intl: ^0.17.0 28 | path_provider: ^2.0.2 29 | percent_indicator: ^3.3.0-nullsafety.1 30 | shimmer: ^2.0.0 31 | smooth_page_indicator: ^0.3.0-nullsafety.0 32 | url_launcher: ^6.0.6 33 | webview_flutter: ^2.0.9 34 | share_plus: ^2.1.4 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | build_runner: ^2.0.3 40 | flutter_lints: ^1.0.0 41 | json_serializable: ^4.1.3 42 | 43 | flutter: 44 | uses-material-design: true 45 | 46 | assets: 47 | - assets/ 48 | 49 | fonts: 50 | - family: Poppins 51 | fonts: 52 | - asset: fonts/Poppins-Black.ttf 53 | - asset: fonts/Poppins-Medium.ttf 54 | -------------------------------------------------------------------------------- /lib/src/models/lists/movie_list_details.dart: -------------------------------------------------------------------------------- 1 | import '../results/movie_result_model.dart'; 2 | 3 | class MovieListDetails { 4 | MovieListDetails({ 5 | this.createdBy, 6 | this.description, 7 | this.favoriteCount, 8 | this.id, 9 | this.items, 10 | this.itemCount, 11 | this.iso6391, 12 | this.name, 13 | this.posterPath, 14 | }); 15 | 16 | String? createdBy; 17 | String? description; 18 | int? favoriteCount; 19 | String? id; 20 | List? items; 21 | int? itemCount; 22 | String? iso6391; 23 | String? name; 24 | String? posterPath; 25 | 26 | factory MovieListDetails.fromJson(Map json) => 27 | MovieListDetails( 28 | createdBy: json["created_by"] as String, 29 | description: json["description"] as String, 30 | favoriteCount: json["favorite_count"] as int, 31 | id: json["id"] as String, 32 | items: json["items"] == null 33 | ? null 34 | : List.from((json["items"] as List) 35 | .map((e) => MovieResultModel.fromJson(e))), 36 | itemCount: json["item_count"] as int, 37 | iso6391: json["iso_639_1"] as String, 38 | name: json["name"] as String, 39 | posterPath: json["poster_path"] as String, 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/views/auth/components/auth_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../configs/configs.dart'; 4 | 5 | class AuthBtn extends StatelessWidget { 6 | const AuthBtn({ 7 | Key? key, 8 | this.onTap, 9 | this.title, 10 | this.btnColor, 11 | }) : super(key: key); 12 | 13 | final void Function()? onTap; 14 | final String? title; 15 | final Color? btnColor; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onTap, 21 | child: Container( 22 | // width: 120, 23 | margin: const EdgeInsets.symmetric(horizontal: 18), 24 | padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), 25 | alignment: Alignment.center, 26 | decoration: BoxDecoration( 27 | color: btnColor ?? primaryblue, 28 | borderRadius: BorderRadius.circular(2), 29 | ), 30 | child: Text( 31 | // _authV3Controller.getSessionState == ViewState.busy 32 | // ? 'Authunticating ...' 33 | // : 34 | title ?? 'Sign In', 35 | style: const TextStyle( 36 | fontSize: m - 4, 37 | color: primaryWhite, 38 | ), 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/views/home/search/tabs/movies_search_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../controllers/search_controller.dart'; 5 | import '../../../../global/loading_spinner.dart'; 6 | import '../../../../helpers/widget_builder_helper.dart'; 7 | import '../../../details/movie_deatils/tabs/movie%20_list/movie_list.dart'; 8 | 9 | class MovieSearchList extends StatelessWidget { 10 | MovieSearchList({Key? key}) : super(key: key); 11 | 12 | final _searchController = Get.find(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetBuilder( 17 | id: 'movie_search_result', 18 | init: _searchController, 19 | builder: (controller) => Obx( 20 | () => WidgetBuilderHelper( 21 | state: _searchController.searchState.value, 22 | onLoadingBuilder: Center(child: LoadingSpinner().fadingCircleSpinner), 23 | onErrorBuilder: const Center( 24 | child: Text('error while loading data ...'), 25 | ), 26 | onSuccessBuilder: Column( 27 | children: [ 28 | MovieList(movies: _searchController.movieSearchResults), 29 | ], 30 | ), 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/views/watchlist/components/watchlist_bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../details/components/bottom_tabbar.dart'; 4 | 5 | class WatchlistTabbarComponent extends StatelessWidget { 6 | WatchlistTabbarComponent({required this.tabMenuItems, Key? key}) 7 | : super(key: key); 8 | final List tabMenuItems; 9 | 10 | final _scrollController = ScrollController(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | height: kToolbarHeight, 16 | alignment: Alignment.centerLeft, 17 | child: SingleChildScrollView( 18 | controller: _scrollController, 19 | physics: const BouncingScrollPhysics(), 20 | padding: const EdgeInsets.symmetric(horizontal: 8), 21 | scrollDirection: Axis.horizontal, 22 | child: SizedBox( 23 | height: kToolbarHeight, 24 | child: Row( 25 | children: List.from(tabMenuItems.map((e) { 26 | int index = tabMenuItems.indexOf(e); 27 | return TabbarItem( 28 | index: index, 29 | title: e, 30 | controller: _scrollController, 31 | ); 32 | })), 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /assets/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/src/views/home/search/tabs/tv_search_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../controllers/search_controller.dart'; 5 | import '../../../../global/loading_spinner.dart'; 6 | import '../../../../helpers/widget_builder_helper.dart'; 7 | import '../../../details/tv_details/tabs/tv_list/tv_list.dart'; 8 | 9 | class TvSearchList extends StatelessWidget { 10 | TvSearchList({Key? key}) : super(key: key); 11 | 12 | final _searchController = Get.find(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetBuilder( 17 | id: 'tv_search_result', 18 | init: _searchController, 19 | builder: (controller) => Obx( 20 | () => WidgetBuilderHelper( 21 | state: _searchController.searchState.value, 22 | onLoadingBuilder: 23 | Center(child: LoadingSpinner().fadingCircleSpinner), 24 | onErrorBuilder: const Center( 25 | child: Text('error while loading data ...'), 26 | ), 27 | onSuccessBuilder: Column( 28 | children: [ 29 | TvList(tv: _searchController.tvSearchResults), 30 | ], 31 | ), 32 | ), 33 | )); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/views/watchlist/tabs/tv_watchlist.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/strings.dart'; 5 | import '../../../controllers/account_controller.dart'; 6 | import '../../../global/loading_spinner.dart'; 7 | import '../../../helpers/widget_builder_helper.dart'; 8 | import '../../details/tv_details/tabs/tv_list/tv_list.dart'; 9 | 10 | class TvWatchlist extends StatelessWidget { 11 | TvWatchlist({Key? key}) : super(key: key); 12 | 13 | final _accountController = Get.find(); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return GetBuilder( 18 | id: 'tv_watchlist', 19 | init: _accountController, 20 | initState: (_) { 21 | _accountController.getWatchlist(mediaType: tvString); 22 | }, 23 | builder: (controller) => Obx( 24 | () => WidgetBuilderHelper( 25 | state: _accountController.watchlistState.value, 26 | onLoadingBuilder: Center(child: LoadingSpinner().fadingCircleSpinner), 27 | onErrorBuilder: const Center( 28 | child: Text('error while loading data ...'), 29 | ), 30 | onSuccessBuilder: Column( 31 | children: [ 32 | TvList(tv: _accountController.tvWatchlist), 33 | ], 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/mixins/avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../configs/configs.dart'; 4 | 5 | mixin AvatarBuilderMixin { 6 | Widget avatarBuilder({double? height, double? width}) => Container( 7 | height: height ?? 68, 8 | width: width ?? 68, 9 | color: primaryDarkBlue.withOpacity(0.4), 10 | child: Stack( 11 | alignment: AlignmentDirectional.bottomCenter, 12 | children: [ 13 | Positioned( 14 | bottom: -26, 15 | child: Container( 16 | height: 62, 17 | width: 62, 18 | decoration: BoxDecoration( 19 | border: Border.all(color: primaryDarkBlue.withOpacity(0.4)), 20 | color: Colors.blueGrey.shade200, 21 | borderRadius: BorderRadius.circular(30), 22 | ), 23 | ), 24 | ), 25 | Positioned( 26 | bottom: 30, 27 | child: Container( 28 | height: 30, 29 | width: 30, 30 | decoration: BoxDecoration( 31 | border: Border.all(color: primaryDarkBlue.withOpacity(0.4)), 32 | color: Colors.blueGrey.shade200, 33 | borderRadius: BorderRadius.circular(30), 34 | ), 35 | ), 36 | ), 37 | ], 38 | ), 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/models/peoples/people_external_ids.dart: -------------------------------------------------------------------------------- 1 | class PeopleExternelIds { 2 | PeopleExternelIds({ 3 | this.id, 4 | this.freebaseMid, 5 | this.freebaseId, 6 | this.imdbId, 7 | this.tvrageId, 8 | this.facebookId, 9 | this.instagramId, 10 | this.twitterId, 11 | }); 12 | 13 | int? id; 14 | String? freebaseMid; 15 | String? freebaseId; 16 | String? imdbId; 17 | int? tvrageId; 18 | String? facebookId; 19 | String? instagramId; 20 | String? twitterId; 21 | 22 | factory PeopleExternelIds.fromJson(Map json) => 23 | _$PeopleExternelIdsFromJson(json); 24 | } 25 | 26 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | /// 28 | /// 29 | /// 30 | /// generated code 31 | /// 32 | /// 33 | /// 34 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | PeopleExternelIds _$PeopleExternelIdsFromJson(Map json) { 37 | return PeopleExternelIds( 38 | id: json['id'] as int?, 39 | freebaseMid: json['freebase_mid'] as String?, 40 | freebaseId: json['freebase_id'] as String?, 41 | imdbId: json['imdb_id'] as String?, 42 | tvrageId: json['tvrage_id'] as int?, 43 | facebookId: json['facebook_id'] as String?, 44 | instagramId: json['instagram_id'] as String?, 45 | twitterId: json['twitter_id'] as String?, 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/routes/route_const_str.dart: -------------------------------------------------------------------------------- 1 | class RoutesPath { 2 | static const initialRoute = "/"; 3 | static const splashScreenRoute = "/splash_screen"; 4 | static const dashboardRoute = "/dashboard"; 5 | static const movieDetailsRoute = "/movie_details"; 6 | static const tvDetailsRoute = "/tv_details"; 7 | static const peopleDetailsRoute = "/people_details"; 8 | static const seasonDetailsRoute = "/season_details"; 9 | static const episodeDetailsRoute = "/episode_details"; 10 | static const episodeCrewRoute = "/episode_crew"; 11 | static const tvCrewRoute = "/tv_crew"; 12 | static const movieCrewRoute = "/movie_crew"; 13 | static const guestStarsRoute = "/guest_stars"; 14 | static const trendingMoviesListRoute = "/trending_movie_list"; 15 | static const movieResultsListRoute = "/movie_results_list"; 16 | static const trendingTvListRoute = "/trending_tv_list"; 17 | static const tvResultsListRoute = "/tv_results_list"; 18 | static const authorizationRoute = "/authorization"; 19 | static const authRoute = "/auth"; 20 | static const movieCollectionListRoute = "/movie_collection_list"; 21 | static const posterViewerRoute = "/poster_viewer"; 22 | static const backdropViewerRoute = "/backdrops_viewer"; 23 | static const posterPreviewRoute = "/poster_preview"; 24 | static const backdropPreviewRoute = "/backdrop_preview"; 25 | static const searchRoute = "/search"; 26 | static const profileRoute = "/profile_page"; 27 | static const userProfileRoute = "/user_profile"; 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/views/details/season_details/tabs/about/season_about_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../controllers/details_controller.dart'; 5 | import '../../../../../controllers/season_controller.dart'; 6 | import '../../../components/genre_component.dart'; 7 | import '../../../components/storyline_text.dart'; 8 | import '../../../tv_details/tabs/about/components/networks.dart'; 9 | 10 | class SeasonAboutTab extends StatelessWidget { 11 | final _detailsController = Get.find(); 12 | 13 | final _seasonController = Get.find(); 14 | 15 | SeasonAboutTab({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Padding( 20 | padding: const EdgeInsets.symmetric(horizontal: 16), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | const SizedBox(height: 18), 25 | StoryLineTextBuilder( 26 | text: 27 | _seasonController.seasonModel.value.overview ?? "storyline"), 28 | const SizedBox(height: 12), 29 | GenreBuilder(genres: _detailsController.tvDetail.value.genres ?? []), 30 | const SizedBox(height: 18), 31 | NetworkBuilder( 32 | networks: _detailsController.tvDetail.value.networks ?? []), 33 | const SizedBox(height: 18), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/views/details/components/genre_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../configs/configs.dart'; 4 | import '../../../models/details/common_details_models.dart'; 5 | import 'header_text.dart'; 6 | 7 | class GenreBuilder extends StatelessWidget { 8 | const GenreBuilder({required this.genres, Key? key}) : super(key: key); 9 | final List genres; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | crossAxisAlignment: CrossAxisAlignment.start, 15 | children: [ 16 | const HeaderBuilder(headerText: "Genre"), 17 | const SizedBox(height: 8), 18 | Wrap( 19 | spacing: 4, 20 | runSpacing: 6, 21 | runAlignment: WrapAlignment.start, 22 | children: List.from( 23 | genres.map( 24 | (e) => Container( 25 | padding: 26 | const EdgeInsets.symmetric(horizontal: 10, vertical: 6), 27 | decoration: BoxDecoration( 28 | color: primaryDarkBlue.withOpacity(0.2), 29 | borderRadius: BorderRadius.circular(12), 30 | ), 31 | child: Text( 32 | e.name ?? "", 33 | style: TextStyle( 34 | fontSize: n - 2, 35 | color: primaryDarkBlue.withOpacity(0.7), 36 | ), 37 | ), 38 | ), 39 | ), 40 | ), 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | 3 | import 'src/services/account_service.dart'; 4 | import 'src/services/auth_v3_service.dart'; 5 | import 'src/services/auth_v4_service.dart'; 6 | import 'src/services/configuration_service.dart'; 7 | import 'src/services/details_service.dart'; 8 | import 'src/services/download_service.dart'; 9 | import 'src/services/lists_services.dart'; 10 | import 'src/services/people_service.dart'; 11 | import 'src/services/results_service.dart'; 12 | import 'src/services/search_service.dart'; 13 | import 'src/services/season_service.dart'; 14 | import 'src/services/trending_results_service.dart'; 15 | 16 | final sl = GetIt.instance; 17 | 18 | void setUp() { 19 | sl.registerLazySingleton(() => ConfigurationService()); 20 | sl.registerLazySingleton(() => ResultsService()); 21 | sl.registerLazySingleton( 22 | () => TrendingResultsService()); 23 | sl.registerLazySingleton(() => DetailsService()); 24 | sl.registerLazySingleton(() => PeopleService()); 25 | sl.registerLazySingleton(() => SeasonService()); 26 | sl.registerLazySingleton(() => AuthV4Service()); 27 | sl.registerLazySingleton(() => AuthV3Service()); 28 | sl.registerLazySingleton(() => AccountService()); 29 | sl.registerLazySingleton(() => ListService()); 30 | sl.registerLazySingleton(() => DownloadService()); 31 | sl.registerLazySingleton(() => SearchService()); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/views/home/components/header_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../configs/configs.dart'; 4 | 5 | class HeaderTile extends StatelessWidget { 6 | const HeaderTile( 7 | {this.onMoreTap, this.subtitle, this.title, this.toggleOption, Key? key}) 8 | : super(key: key); 9 | 10 | final String? title; 11 | final String? subtitle; 12 | final void Function()? onMoreTap; 13 | final Widget? toggleOption; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Padding( 18 | padding: const EdgeInsets.symmetric(horizontal: 12), 19 | child: Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | Row( 23 | children: [ 24 | Text( 25 | title ?? 'title', 26 | style: const TextStyle(fontSize: m - 2), 27 | ), 28 | const SizedBox(width: 4), 29 | Text( 30 | subtitle ?? 'subtitle', 31 | style: const TextStyle( 32 | fontSize: m - 4, 33 | color: primaryblue, 34 | ), 35 | ), 36 | const SizedBox(width: 6), 37 | toggleOption ?? const SizedBox.shrink(), 38 | ], 39 | ), 40 | IconButton( 41 | onPressed: onMoreTap, 42 | icon: Icon( 43 | Icons.arrow_forward, 44 | color: primaryDarkBlue.withOpacity(0.8), 45 | )), 46 | ], 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/utils/auth.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_storage/get_storage.dart'; 2 | 3 | final box = GetStorage('auth'); 4 | 5 | class Auth { 6 | bool get isLoggedIn => box.hasData('sessionId'); 7 | bool get isGuestLoggedIn => box.hasData('guestSessionId'); 8 | 9 | void setSessionId(String sessionId) async => 10 | await box.write('sessionId', sessionId); 11 | void setGuestSessionId(String sessionId) async => 12 | await box.write('guestSessionId', sessionId); 13 | 14 | void setUsername({String? usename}) async => 15 | await box.write('username', usename); 16 | void setFullname({String? fullname}) async => 17 | await box.write('fullname', fullname); 18 | void setUserAvatar({String? url}) async => 19 | await box.write('user_avatar', url); 20 | void setUserGrvatar({String? url}) async => 21 | await box.write('user_gravatar', url); 22 | 23 | void setSearchHistoryMovie(List history) async => 24 | await box.write('history_movie', history); 25 | 26 | void setSearchHistoryTv(List history) async => 27 | await box.write('history_tv', history); 28 | 29 | String get sessionId => box.read('sessionId'); 30 | String get guestSessionId => box.read('guestSessionId'); 31 | 32 | String? get username => box.read('username'); 33 | String? get fullname => box.read('fullname'); 34 | String? get userAvatar => box.read('user_avatar'); 35 | String? get userGravatar => box.read('user_gravatar'); 36 | 37 | List get searchHistoryMovie => box.read('history_movie') ?? []; 38 | List get searchHistoryTv => box.read('history_tv') ?? []; 39 | 40 | void logout() => box.erase(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:get_storage/get_storage.dart'; 5 | 6 | import 'service_locator.dart'; 7 | import 'splash_screen.dart'; 8 | import 'src/bindings/init_bindings.dart'; 9 | import 'src/configs/configs.dart'; 10 | import 'src/routes/route_const_str.dart'; 11 | import 'src/routes/routes.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | // get storage initialization 17 | await GetStorage.init('auth'); 18 | await GetStorage.init('prefs'); 19 | 20 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 21 | 22 | // service locator initialization 23 | setUp(); 24 | 25 | runApp(const MyApp(key: Key('muApp'))); 26 | } 27 | 28 | class MyApp extends StatefulWidget { 29 | const MyApp({Key? key}) : super(key: key); 30 | 31 | @override 32 | State createState() => _MyAppState(); 33 | } 34 | 35 | class _MyAppState extends State { 36 | @override 37 | Widget build(BuildContext context) { 38 | return GetMaterialApp( 39 | initialRoute: RoutesPath.splashScreenRoute, 40 | getPages: Routes.getRoutes(), 41 | initialBinding: InitBindings(), 42 | title: 'Movie DB', 43 | debugShowCheckedModeBanner: false, 44 | theme: ThemeData( 45 | fontFamily: 'Poppins', 46 | canvasColor: primaryWhite, 47 | // primaryColor: primaryWhite, 48 | ), 49 | // home: DashboardPage(), 50 | // home: const AuthPage(), 51 | home: const SplashScreen(), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/init_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'src/controllers/account_controller.dart'; 4 | import 'src/controllers/auth_v3_controller.dart'; 5 | import 'src/controllers/configuration_controller.dart'; 6 | import 'src/controllers/details_controller.dart'; 7 | import 'src/controllers/download_controller.dart'; 8 | import 'src/controllers/list_controller.dart'; 9 | import 'src/controllers/people_controller.dart'; 10 | import 'src/controllers/results_controller.dart'; 11 | import 'src/controllers/search_controller.dart'; 12 | import 'src/controllers/season_controller.dart'; 13 | import 'src/controllers/trending_results_controller.dart'; 14 | import 'src/controllers/utility_controller.dart'; 15 | 16 | class InitBindings extends Bindings { 17 | InitBindings() { 18 | dependencies(); 19 | } 20 | @override 21 | void dependencies() { 22 | Get.lazyPut(() => ConfigurationController(), fenix: true); 23 | Get.lazyPut(() => UtilityController(), fenix: true); 24 | Get.lazyPut(() => ResultsController(), fenix: true); 25 | Get.lazyPut(() => TrendingResultsController(), fenix: true); 26 | Get.lazyPut(() => DetailsController(), fenix: true); 27 | Get.lazyPut(() => PeopleController(), fenix: true); 28 | Get.lazyPut(() => SeasonController(), fenix: true); 29 | Get.lazyPut(() => AuthV3Controller(), fenix: true); 30 | Get.lazyPut(() => AccountController(), fenix: true); 31 | Get.lazyPut(() => ListController(), fenix: true); 32 | Get.lazyPut(() => DownloadController(), fenix: true); 33 | Get.lazyPut(() => AccountController(), fenix: true); 34 | Get.put(SearchController(), permanent: true); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/bindings/init_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/account_controller.dart'; 4 | import '../controllers/auth_v3_controller.dart'; 5 | import '../controllers/configuration_controller.dart'; 6 | import '../controllers/details_controller.dart'; 7 | import '../controllers/download_controller.dart'; 8 | import '../controllers/list_controller.dart'; 9 | import '../controllers/people_controller.dart'; 10 | import '../controllers/results_controller.dart'; 11 | import '../controllers/search_controller.dart'; 12 | import '../controllers/season_controller.dart'; 13 | import '../controllers/trending_results_controller.dart'; 14 | import '../controllers/utility_controller.dart'; 15 | 16 | class InitBindings extends Bindings { 17 | InitBindings() { 18 | dependencies(); 19 | } 20 | @override 21 | void dependencies() { 22 | Get.lazyPut(() => ConfigurationController(), fenix: true); 23 | Get.lazyPut(() => UtilityController(), fenix: true); 24 | Get.lazyPut(() => ResultsController(), fenix: true); 25 | Get.lazyPut(() => TrendingResultsController(), fenix: true); 26 | Get.lazyPut(() => DetailsController(), fenix: true); 27 | Get.lazyPut(() => PeopleController(), fenix: true); 28 | Get.lazyPut(() => SeasonController(), fenix: true); 29 | Get.lazyPut(() => AuthV3Controller(), fenix: true); 30 | Get.lazyPut(() => AccountController(), fenix: true); 31 | Get.lazyPut(() => ListController(), fenix: true); 32 | Get.lazyPut(() => DownloadController(), fenix: true); 33 | Get.lazyPut(() => AccountController(), fenix: true); 34 | Get.put(SearchController(), permanent: true); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/global/add_more_pagination_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../configs/configs.dart'; 4 | import '../controllers/base_controller.dart'; 5 | import 'loading_spinner.dart'; 6 | 7 | class AddMorePaginationBtn extends StatelessWidget { 8 | const AddMorePaginationBtn({ 9 | required this.onTap, 10 | required this.viewState, 11 | Key? key, 12 | }) : super(key: key); 13 | 14 | final void Function()? onTap; 15 | final ViewState viewState; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onTap, 21 | child: Column( 22 | children: [ 23 | Stack( 24 | alignment: AlignmentDirectional.center, 25 | children: [ 26 | Container( 27 | width: 88, 28 | height: 140, 29 | alignment: Alignment.center, 30 | padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), 31 | decoration: BoxDecoration( 32 | color: Colors.black38, 33 | borderRadius: BorderRadius.circular(4), 34 | ), 35 | ), 36 | Center( 37 | child: viewState == ViewState.busy 38 | ? LoadingSpinner().fadingCircleSpinner 39 | : const Icon( 40 | Icons.add, 41 | size: 34, 42 | color: primaryWhite, 43 | ), 44 | ) 45 | ], 46 | ), 47 | const Text("\n"), //for spacing to match with other cards 48 | ], 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Dart 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | branches: [ master ] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | # Note: This workflow uses the latest stable version of the Dart SDK. 22 | # You can specify other versions if desired, see documentation here: 23 | # https://github.com/dart-lang/setup-dart/blob/main/README.md 24 | # - uses: dart-lang/setup-dart@v1 25 | - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 26 | 27 | - name: Install dependencies 28 | run: flutter pub get 29 | 30 | # Uncomment this step to verify the use of 'dart format' on each commit. 31 | # - name: Verify formatting 32 | # run: dart format --output=none --set-exit-if-changed . 33 | 34 | # Consider passing '--fatal-infos' for slightly stricter analysis. 35 | - name: Analyze project source 36 | run: dart analyze 37 | 38 | # Your project will need to have tests in test/ and a dependency on 39 | # package:test for this step to succeed. Note that Flutter projects will 40 | # want to change this to 'flutter test'. 41 | # - name: Run tests 42 | # run: dart test 43 | - name: build apk release 44 | - run: flutter clean 45 | - run: flutter pub get 46 | - run: dart pub get 47 | - run: flutter build apk --split-per-abi 48 | -------------------------------------------------------------------------------- /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 | movie_app 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 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | # error rules 6 | avoid_print: false # Uncomment to disable the `avoid_print` rule 7 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 8 | # always_use_package_imports: true 9 | avoid_empty_else: true 10 | # prefer_relative_imports: true 11 | # avoid_relative_lib_imports: true 12 | avoid_returning_null_for_future: true 13 | avoid_type_to_string: true 14 | avoid_web_libraries_in_flutter: true 15 | cancel_subscriptions: true 16 | empty_statements: true 17 | iterable_contains_unrelated_type: true 18 | list_remove_unrelated_type: true 19 | literal_only_boolean_expressions: true 20 | no_adjacent_strings_in_list: true 21 | no_logic_in_create_state: true 22 | throw_in_finally: true 23 | unrelated_type_equality_checks: true 24 | 25 | # style rules 26 | always_declare_return_types: true 27 | always_put_control_body_on_new_line: true 28 | always_put_required_named_parameters_first: true 29 | always_require_non_null_named_parameters: true 30 | avoid_annotating_with_dynamic: true 31 | # avoid_bool_literals_in_conditional_expressions: true 32 | avoid_catches_without_on_clauses: true 33 | avoid_catching_errors: true 34 | avoid_classes_with_only_static_members: true 35 | avoid_double_and_int_checks: true 36 | avoid_function_literals_in_foreach_calls: true 37 | await_only_futures: true 38 | constant_identifier_names: true 39 | file_names: true 40 | flutter_style_todos: true 41 | # 42 | # Additional information about this file can be found at 43 | # https://dart.dev/guides/language/analysis-options 44 | -------------------------------------------------------------------------------- /lib/src/services/people_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class PeopleService extends BaseService { 8 | /// fetches people details 9 | Future getPeopleDetails({required int personId}) async { 10 | try { 11 | final response = await request( 12 | method: Requests.get, 13 | path: '/3/person/$personId', 14 | header: setHeaders(), 15 | queryParameter: setQueryParameters()); 16 | // ignore: avoid_print 17 | // print(response.statusCode); 18 | return decodeResponse(response); 19 | } on SocketException { 20 | throw FetchDataException('No Internet Connection'); 21 | } on TimeoutException { 22 | throw ServiceNotRespondingException( 23 | 'Service not responding in time please check your Internet Connection'); 24 | } 25 | } 26 | 27 | /// fetches people Movie/tv credits , images , external ids 28 | Future getCreditsDetails( 29 | {required int personId, required String resultType}) async { 30 | try { 31 | final response = await request( 32 | method: Requests.get, 33 | path: '/3/person/$personId/$resultType', 34 | header: setHeaders(), 35 | queryParameter: setQueryParameters()); 36 | // ignore: avoid_print 37 | // print(response.statusCode); 38 | return decodeResponse(response); 39 | } on SocketException { 40 | throw FetchDataException('No Internet Connection'); 41 | } on TimeoutException { 42 | throw ServiceNotRespondingException( 43 | 'Service not responding in time please check your Internet Connection'); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/views/details/components/sliver_appbar_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // title widget 4 | class SABT extends StatefulWidget { 5 | final Widget child; 6 | 7 | const SABT({ 8 | required this.child, 9 | Key? key, 10 | }) : super(key: key); 11 | 12 | @override 13 | _SABTState createState() => _SABTState(); 14 | } 15 | 16 | class _SABTState extends State { 17 | ScrollPosition? _position; 18 | bool? _visible; 19 | 20 | @override 21 | void dispose() { 22 | _removeListener(); 23 | super.dispose(); 24 | } 25 | 26 | @override 27 | void didChangeDependencies() { 28 | super.didChangeDependencies(); 29 | _removeListener(); 30 | _addListener(); 31 | } 32 | 33 | void _addListener() { 34 | _position = Scrollable.of(context)?.position; 35 | _position?.addListener(_positionListener); 36 | _positionListener(); 37 | } 38 | 39 | void _removeListener() { 40 | _position?.removeListener(_positionListener); 41 | } 42 | 43 | void _positionListener() { 44 | final FlexibleSpaceBarSettings? settings = 45 | context.dependOnInheritedWidgetOfExactType(); 46 | bool visible = 47 | settings == null || settings.currentExtent <= settings.minExtent; 48 | if (_visible != visible) { 49 | setState(() { 50 | _visible = visible; 51 | }); 52 | } 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return Visibility( 58 | visible: _visible!, 59 | child: AnimatedOpacity( 60 | duration: const Duration(milliseconds: 300), 61 | opacity: 1, 62 | curve: Curves.easeIn, 63 | child: widget.child, 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/controllers/download_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | 8 | import '../../service_locator.dart'; 9 | import '../configs/configs.dart'; 10 | import '../services/download_service.dart'; 11 | import 'base_controller.dart'; 12 | 13 | class DownloadController extends BaseController { 14 | final _service = sl(); 15 | 16 | var downloadState = ViewState.idle.obs; 17 | var filePath = ''.obs; 18 | var isExpanded = true.obs; 19 | 20 | Directory? dir; 21 | 22 | void setFileUrl(url) => filePath.value = url; 23 | 24 | void resetDownloadState() => downloadState.value = ViewState.idle; 25 | 26 | void toggleExpanded() => isExpanded.value = !isExpanded.value; 27 | 28 | // download image files 29 | void downloadFile({required String url}) async { 30 | downloadState.value = ViewState.busy; 31 | if (Platform.isAndroid) { 32 | dir = await getExternalStorageDirectory(); 33 | print(dir!.path); 34 | } 35 | await _service.downloadFile( 36 | url: url, downloadPath: '${dir!.path}/${Random().hashCode}.jpg'); 37 | Get.showSnackbar(GetBar( 38 | icon: const Icon( 39 | Icons.file_download_done, 40 | size: 26, 41 | color: primaryWhite, 42 | ), 43 | messageText: Text( 44 | "path: ${dir!.path}", 45 | maxLines: 2, 46 | overflow: TextOverflow.ellipsis, 47 | style: const TextStyle( 48 | fontSize: n - 4, 49 | color: primaryWhite, 50 | ), 51 | ), 52 | dismissDirection: SnackDismissDirection.HORIZONTAL, 53 | duration: const Duration(milliseconds: 1800), 54 | )); 55 | 56 | downloadState.value = ViewState.retrived; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ### bugs free version available at _stable*_ branch 2 | 3 | # movie_app 4 | ![TMDb](https://www.themoviedb.org/assets/2/v4/logos/v2/blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg) 5 | 6 | >**"This app project (movie_app) uses the TMDb API but is not endorsed or certified by [TMDb](https://www.themoviedb.org/)."** 7 | 8 | >movie database app with TMDb api data (still in development)* 9 | 10 | 11 | ## screen shots of App 12 | 13 | ![image](https://user-images.githubusercontent.com/48753714/122710524-24cd1900-d280-11eb-81d2-9c71e1c0aca4.png) ![image](https://user-images.githubusercontent.com/48753714/122710579-49c18c00-d280-11eb-9f6b-cf7c66789c28.png) 14 | 15 | ![image](https://user-images.githubusercontent.com/48753714/122710610-6231a680-d280-11eb-8a15-8b541a8c8117.png) ![image](https://user-images.githubusercontent.com/48753714/122710803-bf2d5c80-d280-11eb-982e-c0833fe4a504.png) 16 | 17 | ![image](https://user-images.githubusercontent.com/48753714/122710839-d79d7700-d280-11eb-9853-1bf3abc5305b.png) ![image](https://user-images.githubusercontent.com/48753714/122710885-eedc6480-d280-11eb-96ab-554e031043df.png) 18 | 19 | ![image](https://user-images.githubusercontent.com/48753714/122710941-07e51580-d281-11eb-8f66-5e4f41d6a7d3.png) ![image](https://user-images.githubusercontent.com/48753714/122711040-3a8f0e00-d281-11eb-8c8d-feb9d2d796a3.png) 20 | 21 | ![image](https://user-images.githubusercontent.com/48753714/122711106-5beffa00-d281-11eb-95ff-d6c15ef76c3b.png) ![image](https://user-images.githubusercontent.com/48753714/122711208-82159a00-d281-11eb-9549-533dfdf329df.png) 22 | 23 | ![image](https://user-images.githubusercontent.com/48753714/122711257-9ce80e80-d281-11eb-8314-a12805df1a13.png) ![image](https://user-images.githubusercontent.com/48753714/122711330-b5f0bf80-d281-11eb-8666-3e81a14d0fd6.png) 24 | -------------------------------------------------------------------------------- /lib/src/views/details/tv_details/tabs/tv_list/tv_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../controllers/configuration_controller.dart'; 5 | import '../../../../../global/tv_thumbnail_card.dart'; 6 | import '../../../../../mixins/avatar.dart'; 7 | import '../../../../../models/results/tv_result_model.dart'; 8 | 9 | class TvList extends StatelessWidget with AvatarBuilderMixin { 10 | final List tv; 11 | final String? imageUrl; 12 | final void Function()? onLongPress; 13 | 14 | final _configurationController = Get.find(); 15 | 16 | TvList({required this.tv, this.onLongPress, Key? key, this.imageUrl}) 17 | : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Column( 22 | children: [ 23 | const SizedBox(height: 28), 24 | GridView.builder( 25 | padding: const EdgeInsets.symmetric(horizontal: 14), 26 | shrinkWrap: true, 27 | physics: const NeverScrollableScrollPhysics(), 28 | itemCount: tv.length, 29 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 30 | crossAxisCount: 3, 31 | mainAxisSpacing: 12, 32 | crossAxisSpacing: 12, 33 | mainAxisExtent: 186, 34 | ), 35 | itemBuilder: (context, index) => AbsorbPointer( 36 | absorbing: tv[index].posterPath == null ? true : false, 37 | child: TvThumbnailCard( 38 | onLongPress: onLongPress, 39 | padding: const EdgeInsets.all(0), 40 | tv: tv[index], 41 | imageUrl: 42 | '${_configurationController.posterUrl}${tv[index].posterPath}', 43 | ), 44 | ), 45 | ), 46 | ], 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/views/details/movie_deatils/tabs/movie _list/movie_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../controllers/configuration_controller.dart'; 5 | import '../../../../../global/movie_thumbnail_card.dart'; 6 | import '../../../../../mixins/avatar.dart'; 7 | import '../../../../../models/results/movie_result_model.dart'; 8 | 9 | class MovieList extends StatelessWidget with AvatarBuilderMixin { 10 | final List movies; 11 | // final void Function()? onLongPress; 12 | 13 | final _configurationController = Get.find(); 14 | 15 | MovieList({ 16 | required this.movies, 17 | // this.onLongPress, 18 | Key? key, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Column( 24 | children: [ 25 | const SizedBox(height: 28), 26 | GridView.builder( 27 | padding: const EdgeInsets.symmetric(horizontal: 14), 28 | shrinkWrap: true, 29 | physics: const NeverScrollableScrollPhysics(), 30 | itemCount: movies.length, 31 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 32 | crossAxisCount: 3, 33 | mainAxisSpacing: 12, 34 | crossAxisSpacing: 12, 35 | mainAxisExtent: 186, 36 | ), 37 | itemBuilder: (context, index) => AbsorbPointer( 38 | absorbing: movies[index].posterPath == null ? true : false, 39 | child: MovieThumbnailCard( 40 | // onLongPress: onLongPress, 41 | padding: const EdgeInsets.all(0), 42 | movie: movies[index], 43 | imageUrl: 44 | '${_configurationController.posterUrl}${movies[index].posterPath}', 45 | ), 46 | ), 47 | ), 48 | ], 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/skeletons/page_skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | import 'horizontal_bloc_skeleton.dart'; 5 | 6 | Widget pageSkeleton() { 7 | return SingleChildScrollView( 8 | child: Shimmer.fromColors( 9 | // period: const Duration(seconds: 2), 10 | baseColor: Colors.black26, 11 | highlightColor: Colors.blueGrey.shade50, 12 | child: Column( 13 | children: [ 14 | SizedBox( 15 | height: 80, 16 | child: Row( 17 | mainAxisAlignment: MainAxisAlignment.spaceAround, 18 | children: [ 19 | Container( 20 | height: 46, 21 | width: 46, 22 | decoration: const BoxDecoration( 23 | shape: BoxShape.circle, 24 | color: Colors.black26, 25 | ), 26 | ), 27 | Container( 28 | height: 22, 29 | width: 130, 30 | color: Colors.black26, 31 | ), 32 | Container( 33 | height: 30, 34 | width: 30, 35 | decoration: const BoxDecoration( 36 | // shape: BoxShape.circle, 37 | color: Colors.black26, 38 | ), 39 | ), 40 | ]), 41 | ), 42 | const HorizontalBlocSkeleton(), 43 | const SizedBox(height: 22), 44 | const HorizontalBlocSkeleton(), 45 | const SizedBox(height: 22), 46 | const HorizontalBlocSkeleton(), 47 | const SizedBox(height: 22), 48 | const HorizontalBlocSkeleton(), 49 | const SizedBox(height: 22), 50 | const HorizontalBlocSkeleton(), 51 | const SizedBox(height: 22), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/views/details/people_details/tabs/about/components/chips_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../../configs/configs.dart'; 4 | import '../../../../components/header_text.dart'; 5 | 6 | class ChipsBuilder extends StatelessWidget { 7 | const ChipsBuilder({required this.chips, Key? key}) : super(key: key); 8 | final List chips; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Column( 13 | crossAxisAlignment: CrossAxisAlignment.start, 14 | children: [ 15 | const HeaderBuilder(headerText: "Also known as"), 16 | const SizedBox(height: 8), 17 | chips.isEmpty 18 | ? Text( 19 | 'No data to show at the moment', 20 | style: TextStyle( 21 | color: primaryDarkBlue.withOpacity(0.6), 22 | fontSize: n - 2, 23 | ), 24 | ) 25 | : Wrap( 26 | spacing: 4, 27 | runSpacing: 6, 28 | runAlignment: WrapAlignment.start, 29 | children: List.from( 30 | chips.map( 31 | (e) => Container( 32 | padding: const EdgeInsets.symmetric( 33 | horizontal: 10, vertical: 6), 34 | decoration: BoxDecoration( 35 | color: primaryDarkBlue.withOpacity(0.2), 36 | borderRadius: BorderRadius.circular(12), 37 | ), 38 | child: Text( 39 | e ?? "", 40 | style: TextStyle( 41 | fontSize: n - 2, 42 | color: primaryDarkBlue.withOpacity(0.7), 43 | ), 44 | ), 45 | ), 46 | ), 47 | ), 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/views/details/components/storyline_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/configs.dart'; 5 | import '../../../controllers/utility_controller.dart'; 6 | import 'header_text.dart'; 7 | import 'hide_show_btn.dart'; 8 | 9 | class StoryLineTextBuilder extends GetView { 10 | const StoryLineTextBuilder({ 11 | required this.text, 12 | this.maxLines, 13 | this.headerText, 14 | Key? key, 15 | }) : super(key: key); 16 | 17 | final String text; 18 | final int? maxLines; 19 | final String? headerText; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Column( 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | HeaderBuilder(headerText: headerText ?? "Story Line"), 27 | const SizedBox(height: 8), 28 | text == "" 29 | ? Text( 30 | 'No data at the Moment', 31 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 32 | ) 33 | : Obx( 34 | () => controller.showText.value != true 35 | ? Text( 36 | text, 37 | maxLines: maxLines ?? 4, 38 | overflow: TextOverflow.ellipsis, 39 | style: TextStyle( 40 | color: primaryDarkBlue.withOpacity(0.6), 41 | fontSize: n - 2, 42 | ), 43 | ) 44 | : Text( 45 | text, 46 | style: TextStyle( 47 | color: primaryDarkBlue.withOpacity(0.6), 48 | fontSize: n - 2, 49 | ), 50 | ), 51 | ), 52 | text == "" ? const SizedBox.shrink() : const ToggleHideShowBtn(), 53 | ], 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/views/details/components/sliver_appbar_back_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/color_config.dart'; 5 | 6 | // title widget 7 | class SABTN extends StatefulWidget { 8 | final void Function()? onBack; 9 | 10 | const SABTN({Key? key, this.onBack}) : super(key: key); 11 | 12 | @override 13 | _SABTNState createState() => _SABTNState(); 14 | } 15 | 16 | class _SABTNState extends State { 17 | ScrollPosition? _position; 18 | bool? _visible; 19 | 20 | @override 21 | void dispose() { 22 | _removeListener(); 23 | super.dispose(); 24 | } 25 | 26 | @override 27 | void didChangeDependencies() { 28 | super.didChangeDependencies(); 29 | _removeListener(); 30 | _addListener(); 31 | } 32 | 33 | void _addListener() { 34 | _position = Scrollable.of(context)?.position; 35 | _position?.addListener(_positionListener); 36 | _positionListener(); 37 | } 38 | 39 | void _removeListener() { 40 | _position?.removeListener(_positionListener); 41 | } 42 | 43 | void _positionListener() { 44 | final FlexibleSpaceBarSettings? settings = 45 | context.dependOnInheritedWidgetOfExactType(); 46 | bool visible = 47 | settings == null || settings.currentExtent <= settings.minExtent; 48 | if (_visible != visible) { 49 | setState(() { 50 | _visible = visible; 51 | }); 52 | } 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return AnimatedOpacity( 58 | duration: const Duration(milliseconds: 300), 59 | opacity: 1, 60 | curve: Curves.easeIn, 61 | child: IconButton( 62 | onPressed: widget.onBack ?? 63 | () { 64 | Get.back(); 65 | }, 66 | icon: Icon( 67 | Icons.arrow_back, 68 | color: _visible == false ? primaryWhite : primaryDarkBlue, 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/youtube_iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/src/views/profile/components/guest_credentials.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/configs.dart'; 5 | import '../../../controllers/auth_v3_controller.dart'; 6 | 7 | class GuestUserCredientials extends GetView { 8 | const GuestUserCredientials({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Column( 13 | crossAxisAlignment: CrossAxisAlignment.start, 14 | children: [ 15 | Container( 16 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), 17 | decoration: BoxDecoration( 18 | color: primaryDarkBlue, 19 | borderRadius: BorderRadius.circular(12), 20 | ), 21 | child: const Text( 22 | 'Guest*', 23 | style: TextStyle( 24 | fontSize: m + 2, 25 | fontWeight: FontWeight.w700, 26 | color: primaryblue, 27 | ), 28 | ), 29 | ), 30 | const SizedBox(height: 12), 31 | 32 | // signout 33 | GestureDetector( 34 | onTap: () { 35 | controller.logoutV3(); 36 | }, 37 | child: Container( 38 | color: Colors.transparent, 39 | padding: const EdgeInsets.symmetric(vertical: 8), 40 | child: Row( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: const [ 43 | Text( 44 | 'Signout', 45 | style: TextStyle( 46 | color: primaryblue, 47 | fontSize: n, 48 | fontWeight: FontWeight.w600, 49 | ), 50 | ), 51 | Icon( 52 | Icons.arrow_forward_ios, 53 | size: 18, 54 | color: Colors.black54, 55 | ), 56 | ], 57 | ), 58 | ), 59 | ), 60 | ], 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/models/peoples/people_model.dart: -------------------------------------------------------------------------------- 1 | class PeopleModel { 2 | PeopleModel({ 3 | this.adult, 4 | this.alsoKnownAs, 5 | this.biography, 6 | this.birthday, 7 | this.deathday, 8 | this.gender, 9 | this.homepage, 10 | this.id, 11 | this.imdbId, 12 | this.knownForDepartment, 13 | this.name, 14 | this.placeOfBirth, 15 | this.popularity, 16 | this.profilePath, 17 | }); 18 | 19 | bool? adult; 20 | List? alsoKnownAs; 21 | String? biography; 22 | String? birthday; 23 | dynamic deathday; 24 | int? gender; 25 | dynamic homepage; 26 | int? id; 27 | String? imdbId; 28 | String? knownForDepartment; 29 | String? name; 30 | String? placeOfBirth; 31 | double? popularity; 32 | String? profilePath; 33 | 34 | factory PeopleModel.fromJson(Map json) => 35 | _$PeopleModelFromJson(json); 36 | } 37 | 38 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 39 | /// 40 | /// 41 | /// 42 | /// generated code 43 | /// 44 | /// 45 | /// 46 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 47 | 48 | PeopleModel _$PeopleModelFromJson(Map json) { 49 | return PeopleModel( 50 | adult: json['adult'] as bool?, 51 | alsoKnownAs: (json['also_known_as'] as List?) 52 | ?.map((e) => e as String) 53 | .toList(), 54 | biography: json['biography'] as String?, 55 | birthday: json['birthday'] as String?, 56 | deathday: json['deathday'] as String?, 57 | gender: json['gender'] as int?, 58 | homepage: json['homepage'], 59 | id: json['id'] as int?, 60 | imdbId: json['imdb_id'] as String?, 61 | knownForDepartment: json['known_for_department'] as String?, 62 | name: json['name'] as String?, 63 | placeOfBirth: json['place_of_birth'] as String?, 64 | popularity: (json['popularity'] as num?)?.toDouble(), 65 | profilePath: json['profile_path'] as String?, 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/services/results_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import '../exceptions/app_exceptions.dart'; 5 | import 'base_service.dart'; 6 | 7 | class ResultsService extends BaseService { 8 | // movie results service 9 | Future getMovieResults({ 10 | required String resultType, 11 | String page = "", 12 | String region = "", 13 | }) async { 14 | try { 15 | final response = await request( 16 | method: Requests.get, 17 | path: "/3/movie/$resultType", 18 | header: setHeaders(), 19 | queryParameter: setQueryParameters(query: {"page": page}), 20 | ); 21 | // ignore: avoid_print 22 | // print('$resultType MOVIE RESULTS STATUS => ${response.statusCode}'); 23 | // ignore: avoid_print 24 | // print('PAGE => $page'); 25 | 26 | return decodeResponse(response)['results']; 27 | } on SocketException { 28 | throw FetchDataException('No Internet Connection'); 29 | } on TimeoutException { 30 | throw ServiceNotRespondingException( 31 | 'Service not responding in time please check your Internet Connection'); 32 | } 33 | } 34 | 35 | // tv results service 36 | Future getTvResults({ 37 | required String resultType, 38 | String page = "", 39 | }) async { 40 | try { 41 | final response = await request( 42 | method: Requests.get, 43 | path: "/3/tv/$resultType", 44 | header: setHeaders(), 45 | queryParameter: setQueryParameters(query: {"page": page}), 46 | ); 47 | // ignore: avoid_print 48 | // print('$resultType TV RESULTS STATUS => ${response.statusCode}'); 49 | // ignore: avoid_print 50 | // print('PAGE => ${decodeResponse(response)['page']}'); 51 | 52 | return decodeResponse(response)['results']; 53 | } on SocketException { 54 | throw FetchDataException('No Internet Connection'); 55 | } on TimeoutException { 56 | throw ServiceNotRespondingException( 57 | 'Service not responding in time please check your Internet Connection'); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/views/details/tv_details/tabs/tv_list/similar_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import 'tv_list.dart'; 11 | 12 | class TvSimilarTab extends StatelessWidget { 13 | TvSimilarTab({Key? key}) : super(key: key); 14 | 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return GetBuilder( 21 | id: 'tv_similar', 22 | init: _detailsController, 23 | initState: (_) { 24 | _detailsController.getOtherDetails( 25 | resultType: tvString, 26 | id: _resultsController.tvId, 27 | appendTo: similarString); 28 | }, 29 | builder: (controller) => WidgetBuilderHelper( 30 | state: _detailsController.similarState.value, 31 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 32 | onErrorBuilder: const Center( 33 | child: Text('error while loading data ...'), 34 | ), 35 | onSuccessBuilder: _detailsController.similarTv.value.results == null || 36 | _detailsController.similarTv.value.results!.isEmpty 37 | ? SizedBox( 38 | height: 80, 39 | child: Center( 40 | child: Text( 41 | 'No Similar Movies at the Moment', 42 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 43 | ), 44 | ), 45 | ) 46 | : Column( 47 | children: [ 48 | TvList(tv: _detailsController.similarTv.value.results ?? []), 49 | ], 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/skeletons/horizontal_bloc_skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HorizontalBlocSkeleton extends StatelessWidget { 4 | const HorizontalBlocSkeleton({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | margin: const EdgeInsets.fromLTRB(6, 6, 6, 6), 10 | child: Column( 11 | crossAxisAlignment: CrossAxisAlignment.start, 12 | children: [ 13 | Row( 14 | children: [ 15 | Container( 16 | height: 20, 17 | width: 120, 18 | color: Colors.black26, 19 | ), 20 | const SizedBox(width: 12), 21 | Container( 22 | height: 20, 23 | width: 60, 24 | color: Colors.black26, 25 | ), 26 | ], 27 | ), 28 | const SizedBox(height: 12), 29 | SingleChildScrollView( 30 | physics: const NeverScrollableScrollPhysics(), 31 | scrollDirection: Axis.horizontal, 32 | child: Row( 33 | children: List.generate( 34 | 5, 35 | (index) => Column( 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | Container( 39 | margin: const EdgeInsets.fromLTRB(0, 0, 6, 0), 40 | decoration: BoxDecoration( 41 | color: Colors.black26, 42 | borderRadius: BorderRadius.circular(4), 43 | ), 44 | width: 88, 45 | height: 140, 46 | ), 47 | const SizedBox(height: 8), 48 | Container( 49 | height: 20, 50 | width: 66, 51 | color: Colors.black26, 52 | ), 53 | ], 54 | ), 55 | ), 56 | ), 57 | ), 58 | ], 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/views/auth/components/webview_request_authorization.dart: -------------------------------------------------------------------------------- 1 | /// 2 | /// this webview is integrated for TMDb v4 API 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | import 'package:flutter/material.dart'; 17 | import 'package:get/get.dart'; 18 | import 'package:movie_app/service_locator.dart'; 19 | import 'package:movie_app/src/services/auth_v4_service.dart'; 20 | import 'package:webview_flutter/webview_flutter.dart'; 21 | 22 | class AuthorizeRequestToken extends StatefulWidget { 23 | const AuthorizeRequestToken({Key? key}) : super(key: key); 24 | 25 | @override 26 | _AuthorizeRequestTokenState createState() => _AuthorizeRequestTokenState(); 27 | } 28 | 29 | class _AuthorizeRequestTokenState extends State { 30 | final service = sl(); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return SafeArea( 35 | child: WebView( 36 | initialUrl: Get.parameters['url'], 37 | javascriptMode: JavascriptMode.unrestricted, 38 | onPageFinished: (url) async { 39 | if (url == "https://www.themoviedb.org/auth/access/approve") { 40 | Get.dialog( 41 | AlertDialog( 42 | actions: [ 43 | TextButton( 44 | onPressed: () { 45 | service 46 | .createV4AccessToken( 47 | requestToken: 48 | Get.parameters['requestToken'] ?? "") 49 | .then((value) { 50 | Get.back(); 51 | Get.back(); 52 | }); 53 | }, 54 | child: const Text("Continue"), 55 | ), 56 | ], 57 | title: const Text('Authorized'), 58 | content: const Text( 59 | '3rd party Authuntication request has been authorized!!'), 60 | ), 61 | barrierDismissible: false, 62 | ); 63 | } 64 | }, 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /assets/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.movie_app" 47 | minSdkVersion 19 48 | targetSdkVersion 31 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/models/account_model.dart: -------------------------------------------------------------------------------- 1 | class AccountModel { 2 | AccountModel({ 3 | this.avatar, 4 | this.id, 5 | this.iso6391, 6 | this.iso31661, 7 | this.name, 8 | this.includeAdult, 9 | this.username, 10 | }); 11 | 12 | Avatar? avatar; 13 | int? id; 14 | String? iso6391; 15 | String? iso31661; 16 | String? name; 17 | bool? includeAdult; 18 | String? username; 19 | 20 | factory AccountModel.fromJson(Map json) => AccountModel( 21 | avatar: Avatar.fromJson(json["avatar"]), 22 | id: json["id"], 23 | iso6391: json["iso_639_1"], 24 | iso31661: json["iso_3166_1"], 25 | name: json["name"], 26 | includeAdult: json["include_adult"], 27 | username: json["username"], 28 | ); 29 | 30 | Map toJson() => { 31 | "avatar": avatar!.toJson(), 32 | "id": id, 33 | "iso_639_1": iso6391, 34 | "iso_3166_1": iso31661, 35 | "name": name, 36 | "include_adult": includeAdult, 37 | "username": username, 38 | }; 39 | } 40 | 41 | class Avatar { 42 | Avatar({ 43 | this.gravatar, 44 | this.tmdb, 45 | }); 46 | 47 | Gravatar? gravatar; 48 | Tmdb? tmdb; 49 | 50 | factory Avatar.fromJson(Map json) => Avatar( 51 | gravatar: Gravatar.fromJson(json["gravatar"]), 52 | tmdb: Tmdb.fromJson(json["tmdb"]), 53 | ); 54 | 55 | Map toJson() => { 56 | "gravatar": gravatar!.toJson(), 57 | "tmdb": tmdb!.toJson(), 58 | }; 59 | } 60 | 61 | class Gravatar { 62 | Gravatar({ 63 | this.hash, 64 | }); 65 | 66 | String? hash; 67 | 68 | factory Gravatar.fromJson(Map json) => Gravatar( 69 | hash: json["hash"], 70 | ); 71 | 72 | Map toJson() => { 73 | "hash": hash, 74 | }; 75 | } 76 | 77 | class Tmdb { 78 | Tmdb({ 79 | this.avatarPath, 80 | }); 81 | 82 | String? avatarPath; 83 | 84 | factory Tmdb.fromJson(Map json) => Tmdb( 85 | avatarPath: json["avatar_path"], 86 | ); 87 | 88 | Map toJson() => { 89 | "avatar_path": avatarPath, 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/views/details/tv_details/tabs/tv_list/recommended_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import 'tv_list.dart'; 11 | 12 | class TvRecommendedTab extends StatelessWidget { 13 | TvRecommendedTab({Key? key}) : super(key: key); 14 | 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return GetBuilder( 21 | id: 'tv_recommended', 22 | init: _detailsController, 23 | initState: (_) { 24 | _detailsController.getOtherDetails( 25 | resultType: tvString, 26 | id: _resultsController.tvId, 27 | appendTo: recommendedSTring); 28 | }, 29 | builder: (controller) => WidgetBuilderHelper( 30 | state: _detailsController.recommendedState.value, 31 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 32 | onErrorBuilder: const Center( 33 | child: Text('error while loading data ...'), 34 | ), 35 | onSuccessBuilder: _detailsController.recommendedTv.value.results == 36 | null || 37 | _detailsController.recommendedTv.value.results!.isEmpty 38 | ? SizedBox( 39 | height: 80, 40 | child: Center( 41 | child: Text( 42 | 'No Recommended TV Series at the Moment', 43 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 44 | ), 45 | ), 46 | ) 47 | : Column( 48 | children: [ 49 | TvList( 50 | tv: _detailsController.recommendedTv.value.results ?? []), 51 | ], 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/configs/strings.dart: -------------------------------------------------------------------------------- 1 | const String movieString = "movie"; 2 | const String moviesString = "movies"; 3 | const String tvString = "tv"; 4 | const String allString = "all"; 5 | const String peopleString = "people"; 6 | const String personString = "person"; 7 | const String dayString = "day"; 8 | const String weekString = "week"; 9 | const String popularString = "popular"; 10 | const String topRatedString = "top_rated"; 11 | const String onTheAirString = "on_the_air"; 12 | const String airingTodayString = "airing_today"; 13 | const String latestString = "latest"; 14 | const String nowPlayingString = "now_playing"; 15 | const String upcomingString = "upcoming"; 16 | const String trendingString = "trending"; 17 | 18 | const String imagesString = "images"; 19 | const String videosString = "videos"; 20 | const String similarString = "similar"; 21 | const String recommendedSTring = "recommendations"; 22 | const String accountStateString = "account_states"; 23 | const String reviewsString = "reviews"; 24 | const String creditsString = "credits"; 25 | const String externalIdsString = "external_ids"; 26 | 27 | const String movieCreditsString = "movie_credits"; 28 | const String tvCreditsString = "tv_credits"; 29 | 30 | const String facebookString = "https://www.facebook.com"; 31 | const String instagramString = "https://www.instagram.com"; 32 | const String twitterString = "https://twitter.com"; 33 | const String imdbString = "https://www.imdb.com/name"; 34 | 35 | // ratings strings 36 | const String rateQuestionString = "Do you like it ?"; // question 37 | const String terribleString = "Terrible"; //0.5 - 1.5 38 | const String poorString = "Poor"; //1.5> - 3 39 | const String notBadString = "Not Bad"; //3> - 4 40 | const String okayString = "It's Okay!"; // 4> - 5.5 41 | const String goodString = "Good"; // 5.5> - 6.5 42 | const String greatString = "Great"; //6.5> - 8 43 | const String wonderfulString = "Wonderful"; //8> - <=10 44 | 45 | // youtube urls 46 | const youtubeUrlString = "https://youtu.be"; 47 | const youtubeThumbnailString = "https://img.youtube.com/vi"; 48 | const maxQualityString = "/maxresdefault.jpg"; 49 | const hqQualityString = "/hqdefault.jpg"; 50 | const mqQualityString = "/mqdefault.jpg"; 51 | const sdQualityString = "/sddefault.jpg"; 52 | -------------------------------------------------------------------------------- /lib/src/views/details/movie_deatils/tabs/movie _list/similar_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import '../movie%20_list/movie_list.dart'; 11 | 12 | class MovieSimilarTab extends StatelessWidget { 13 | MovieSimilarTab({Key? key}) : super(key: key); 14 | 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return GetBuilder( 21 | id: 'movie_similar', 22 | init: _detailsController, 23 | initState: (_) { 24 | _detailsController.getOtherDetails( 25 | resultType: movieString, 26 | id: _resultsController.movieId, 27 | appendTo: similarString); 28 | }, 29 | builder: (controller) => WidgetBuilderHelper( 30 | state: _detailsController.similarState.value, 31 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 32 | onErrorBuilder: const Center( 33 | child: Text('error while loading data ...'), 34 | ), 35 | onSuccessBuilder: _detailsController.similarMovie.value.results == 36 | null || 37 | _detailsController.similarMovie.value.results!.isEmpty 38 | ? SizedBox( 39 | height: 80, 40 | child: Center( 41 | child: Text( 42 | 'No Similar Movies at the Moment', 43 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 44 | ), 45 | ), 46 | ) 47 | : Column( 48 | children: [ 49 | MovieList( 50 | movies: 51 | _detailsController.similarMovie.value.results ?? []), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/models/configurations/api_configuration_model.dart: -------------------------------------------------------------------------------- 1 | class ApiConfigurationModel { 2 | ApiConfigurationModel({ 3 | required this.images, 4 | required this.changeKeys, 5 | }); 6 | 7 | Images images; 8 | List changeKeys; 9 | 10 | factory ApiConfigurationModel.fromJson(Map json) => 11 | ApiConfigurationModel( 12 | images: Images.fromJson(json["images"]), 13 | changeKeys: List.from(json["change_keys"].map((x) => x)), 14 | ); 15 | 16 | Map toJson() => { 17 | "images": images.toJson(), 18 | "change_keys": List.from(changeKeys.map((x) => x)), 19 | }; 20 | } 21 | 22 | class Images { 23 | Images({ 24 | required this.baseUrl, 25 | required this.secureBaseUrl, 26 | required this.backdropSizes, 27 | required this.logoSizes, 28 | required this.posterSizes, 29 | required this.profileSizes, 30 | required this.stillSizes, 31 | }); 32 | 33 | String baseUrl; 34 | String secureBaseUrl; 35 | List backdropSizes; 36 | List logoSizes; 37 | List posterSizes; 38 | List profileSizes; 39 | List stillSizes; 40 | 41 | factory Images.fromJson(Map json) => Images( 42 | baseUrl: json["base_url"], 43 | secureBaseUrl: json["secure_base_url"], 44 | backdropSizes: List.from(json["backdrop_sizes"].map((x) => x)), 45 | logoSizes: List.from(json["logo_sizes"].map((x) => x)), 46 | posterSizes: List.from(json["poster_sizes"].map((x) => x)), 47 | profileSizes: List.from(json["profile_sizes"].map((x) => x)), 48 | stillSizes: List.from(json["still_sizes"].map((x) => x)), 49 | ); 50 | 51 | Map toJson() => { 52 | "base_url": baseUrl, 53 | "secure_base_url": secureBaseUrl, 54 | "backdrop_sizes": List.from(backdropSizes.map((x) => x)), 55 | "logo_sizes": List.from(logoSizes.map((x) => x)), 56 | "poster_sizes": List.from(posterSizes.map((x) => x)), 57 | "profile_sizes": List.from(profileSizes.map((x) => x)), 58 | "still_sizes": List.from(stillSizes.map((x) => x)), 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/views/details/components/poster_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../../../configs/configs.dart'; 6 | import '../../../controllers/configuration_controller.dart'; 7 | 8 | class PosterCard extends GetView { 9 | const PosterCard({required this.imageUrl, Key? key}) : super(key: key); 10 | final String? imageUrl; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return GestureDetector( 15 | onTap: imageUrl == null 16 | ? null 17 | : () { 18 | Get.toNamed( 19 | '/poster_preview', 20 | arguments: {"filePath": imageUrl}, 21 | ); 22 | }, 23 | child: Container( 24 | padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), 25 | child: ClipRRect( 26 | borderRadius: BorderRadius.circular(4), 27 | child: imageUrl == null 28 | ? Container( 29 | alignment: Alignment.center, 30 | width: 94, 31 | height: 140, 32 | color: Colors.black12, 33 | child: const Icon( 34 | Icons.error_outline, 35 | color: primaryWhite, 36 | size: 34, 37 | ), 38 | ) 39 | : CachedNetworkImage( 40 | width: 94, 41 | height: 140, 42 | fit: BoxFit.fill, 43 | errorWidget: (context, url, error) => Container( 44 | alignment: Alignment.center, 45 | decoration: const BoxDecoration( 46 | color: Colors.black12, 47 | ), 48 | child: const Icon( 49 | Icons.error, 50 | color: primaryWhite, 51 | ), 52 | ), 53 | imageUrl: '${controller.posterUrl}$imageUrl', 54 | placeholder: (context, url) => Container( 55 | color: Colors.black12, 56 | ), 57 | ), 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/controllers/configuration_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../service_locator.dart'; 4 | import '../models/configurations/api_configuration_model.dart'; 5 | import '../services/configuration_service.dart'; 6 | import 'base_controller.dart'; 7 | 8 | class ConfigurationController extends BaseController { 9 | @override 10 | void onInit() { 11 | super.onInit(); 12 | getConfigurations(); 13 | } 14 | 15 | final _service = sl(); 16 | var configuration = ApiConfigurationModel( 17 | changeKeys: [], 18 | images: Images( 19 | baseUrl: "", 20 | secureBaseUrl: "", 21 | backdropSizes: [], 22 | logoSizes: [], 23 | posterSizes: [], 24 | profileSizes: [], 25 | stillSizes: [], 26 | )).obs; 27 | 28 | var configState = ViewState.idle.obs; 29 | 30 | String _posterUrl = ""; 31 | String _backDropUrl = ""; 32 | String _profileUrl = ""; 33 | String _logoUrl = ""; 34 | String _stillUrl = ""; 35 | 36 | String get posterUrl => _posterUrl; 37 | String get backDropUrl => _backDropUrl; 38 | String get profileUrl => _profileUrl; 39 | String get logoUrl => _logoUrl; 40 | String get stillUrl => _stillUrl; 41 | 42 | void getConfigurations() async { 43 | configState.value = ViewState.busy; 44 | await _service.getConfiguration().then((value) { 45 | // if (value != null) { 46 | configuration.value = ApiConfigurationModel.fromJson(value); 47 | _posterUrl = 48 | '${configuration.value.images.secureBaseUrl}${configuration.value.images.posterSizes[3]}'; 49 | _backDropUrl = 50 | '${configuration.value.images.secureBaseUrl}${configuration.value.images.backdropSizes[1]}'; 51 | _profileUrl = 52 | '${configuration.value.images.secureBaseUrl}${configuration.value.images.profileSizes[2]}'; 53 | _logoUrl = 54 | '${configuration.value.images.secureBaseUrl}${configuration.value.images.logoSizes[3]}'; 55 | _stillUrl = 56 | '${configuration.value.images.secureBaseUrl}${configuration.value.images.stillSizes[3]}'; 57 | // ignore: avoid_print 58 | // print(configuration.value.changeKeys); 59 | configState.value = ViewState.retrived; 60 | // } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/views/details/movie_deatils/tabs/movie _list/collections_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../controllers/details_controller.dart'; 6 | import '../../../../../global/loading_spinner.dart'; 7 | import '../../../../../helpers/widget_builder_helper.dart'; 8 | import '../movie%20_list/movie_list.dart'; 9 | 10 | class MoviesCollectionList extends StatelessWidget { 11 | MoviesCollectionList({required this.collectionId, Key? key}) 12 | : super(key: key); 13 | 14 | final String collectionId; 15 | 16 | final _detailsController = Get.find(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | elevation: 0.5, 23 | title: Text( 24 | _detailsController.movieDetail.value.belongsToCollection!.name ?? 25 | "collection name"), 26 | ), 27 | body: GetBuilder( 28 | id: 'movie_collections', 29 | init: _detailsController, 30 | initState: (_) { 31 | _detailsController.getCollectionsDetails(collectionId: collectionId); 32 | }, 33 | builder: (controller) => WidgetBuilderHelper( 34 | state: _detailsController.collectionsDetailsState.value, 35 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 36 | onErrorBuilder: const Center( 37 | child: Text('error while loading data ...'), 38 | ), 39 | onSuccessBuilder: _detailsController.movieCollections.isEmpty 40 | ? SizedBox( 41 | height: 80, 42 | child: Center( 43 | child: Text( 44 | 'No Movies at the Moment', 45 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 46 | ), 47 | ), 48 | ) 49 | : SingleChildScrollView( 50 | child: Column( 51 | children: [ 52 | MovieList(movies: _detailsController.movieCollections), 53 | ], 54 | ), 55 | ), 56 | ), 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/views/details/movie_deatils/tabs/movie _list/recommended_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import '../movie%20_list/movie_list.dart'; 11 | 12 | class MovieRecommendedTab extends StatelessWidget { 13 | MovieRecommendedTab({Key? key}) : super(key: key); 14 | 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return GetBuilder( 21 | id: 'movie_recommended', 22 | init: _detailsController, 23 | initState: (_) { 24 | _detailsController.getOtherDetails( 25 | resultType: movieString, 26 | id: _resultsController.movieId, 27 | appendTo: recommendedSTring); 28 | }, 29 | builder: (controller) => WidgetBuilderHelper( 30 | state: _detailsController.recommendedState.value, 31 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 32 | onErrorBuilder: const Center( 33 | child: Text('error while loading data ...'), 34 | ), 35 | onSuccessBuilder: _detailsController.recommendedMovie.value.results == 36 | null || 37 | _detailsController.recommendedMovie.value.results!.isEmpty 38 | ? SizedBox( 39 | height: 80, 40 | child: Center( 41 | child: Text( 42 | 'No Recommended Movies at the Moment', 43 | style: TextStyle(color: primaryDarkBlue.withOpacity(0.6)), 44 | ), 45 | ), 46 | ) 47 | : Column( 48 | children: [ 49 | MovieList( 50 | movies: 51 | _detailsController.recommendedMovie.value.results ?? 52 | []), 53 | ], 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 16 | 20 | 24 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/src/models/results/movie_result_model.dart: -------------------------------------------------------------------------------- 1 | class MovieResultModel { 2 | MovieResultModel({ 3 | this.adult, 4 | this.backdropPath, 5 | this.genreIds, 6 | this.id, 7 | this.originalLanguage, 8 | this.originalTitle, 9 | this.overview, 10 | this.popularity, 11 | this.posterPath, 12 | this.releaseDate, 13 | this.title, 14 | this.video, 15 | this.voteAverage, 16 | this.voteCount, 17 | }); 18 | 19 | bool? adult; 20 | String? backdropPath; 21 | List? genreIds; 22 | int? id; 23 | String? originalLanguage; 24 | String? originalTitle; 25 | String? overview; 26 | double? popularity; 27 | String? posterPath; 28 | DateTime? releaseDate; 29 | String? title; 30 | bool? video; 31 | double? voteAverage; 32 | int? voteCount; 33 | 34 | factory MovieResultModel.fromJson(Map json) => 35 | MovieResultModel( 36 | adult: json['adult'] as bool?, 37 | backdropPath: json['backdrop_path'] as String?, 38 | genreIds: (json['genre_ids'] as List?) 39 | ?.map((e) => e as int) 40 | .toList(), 41 | id: json['id'] as int?, 42 | originalLanguage: json['original_language'] as String?, 43 | originalTitle: json['original_title'] as String?, 44 | overview: json['overview'] as String?, 45 | popularity: (json['popularity'] as num?)?.toDouble(), 46 | posterPath: json['poster_path'] as String?, 47 | releaseDate: json['release_date'] == null || json['release_date'] == "" 48 | ? null 49 | : DateTime.parse(json['release_date'] as String), 50 | title: json['title'] as String?, 51 | video: json['video'] as bool?, 52 | voteAverage: (json['vote_average'] as num?)?.toDouble(), 53 | voteCount: json['vote_count'] as int?, 54 | ); 55 | 56 | Map toJson() => { 57 | 'adult': adult, 58 | 'backdropPath': backdropPath, 59 | 'genreIds': genreIds, 60 | 'id': id, 61 | 'originalLanguage': originalLanguage, 62 | 'originalTitle': originalTitle, 63 | 'overview': overview, 64 | 'popularity': popularity, 65 | 'posterPath': posterPath, 66 | 'releaseDate': releaseDate?.toIso8601String(), 67 | 'title': title, 68 | 'video': video, 69 | 'voteAverage': voteAverage, 70 | 'voteCount': voteCount, 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/models/results/tv_result_model.dart: -------------------------------------------------------------------------------- 1 | class TvResultsModel { 2 | TvResultsModel({ 3 | this.backdropPath, 4 | this.firstAirDate, 5 | this.genreIds, 6 | this.id, 7 | this.name, 8 | this.originCountry, 9 | this.originalLanguage, 10 | this.originalName, 11 | this.overview, 12 | this.popularity, 13 | this.posterPath, 14 | this.voteAverage, 15 | this.voteCount, 16 | }); 17 | 18 | String? backdropPath; 19 | DateTime? firstAirDate; 20 | List? genreIds; 21 | int? id; 22 | String? name; 23 | List? originCountry; 24 | String? originalLanguage; 25 | String? originalName; 26 | String? overview; 27 | double? popularity; 28 | String? posterPath; 29 | double? voteAverage; 30 | int? voteCount; 31 | 32 | factory TvResultsModel.fromJson(Map json) => TvResultsModel( 33 | backdropPath: json['backdrop_path'] as String?, 34 | firstAirDate: 35 | json['first_air_date'] == null || json['first_air_date'] == "" 36 | ? null 37 | : DateTime.parse(json['first_air_date'] as String), 38 | genreIds: (json['genre_ids'] as List?) 39 | ?.map((e) => e as int) 40 | .toList(), 41 | id: json['id'] as int?, 42 | name: json['name'] as String?, 43 | originCountry: (json['origin_country'] as List?) 44 | ?.map((e) => e as String) 45 | .toList(), 46 | originalLanguage: json['original_language'] as String?, 47 | originalName: json['original_name'] as String?, 48 | overview: json['overview'] as String?, 49 | popularity: (json['popularity'] as num?)?.toDouble(), 50 | posterPath: json['poster_path'] as String?, 51 | voteAverage: (json['vote_average'] as num?)?.toDouble(), 52 | voteCount: json['vote_count'] as int?, 53 | ); 54 | 55 | Map toJson() => { 56 | 'backdropPath': backdropPath, 57 | 'firstAirDate': firstAirDate?.toIso8601String(), 58 | 'genreIds': genreIds, 59 | 'id': id, 60 | 'name': name, 61 | 'originCountry': originCountry, 62 | 'originalLanguage': originalLanguage, 63 | 'originalName': originalName, 64 | 'overview': overview, 65 | 'popularity': popularity, 66 | 'posterPath': posterPath, 67 | 'voteAverage': voteAverage, 68 | 'voteCount': voteCount, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/imdb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 10 | 15 | 18 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/src/views/details/tv_details/tabs/about/components/networks.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../../../../../../configs/color_config.dart'; 6 | import '../../../../../../configs/configs.dart'; 7 | import '../../../../../../controllers/configuration_controller.dart'; 8 | import '../../../../../../models/details/tv_details_model.dart'; 9 | import '../../../../components/header_text.dart'; 10 | 11 | class NetworkBuilder extends GetView { 12 | const NetworkBuilder({required this.networks, Key? key}) : super(key: key); 13 | final List networks; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | const HeaderBuilder(headerText: "Networks"), 21 | const SizedBox(height: 8), 22 | Wrap( 23 | spacing: 4, 24 | runSpacing: 6, 25 | runAlignment: WrapAlignment.start, 26 | children: List.from( 27 | networks.map( 28 | (e) => Container( 29 | margin: const EdgeInsets.only(right: 4), 30 | padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), 31 | decoration: BoxDecoration( 32 | color: primaryDarkBlue.withOpacity(0.2), 33 | borderRadius: BorderRadius.circular(12), 34 | ), 35 | child: e.logoPath == null || e.logoPath == "" 36 | ? const SizedBox( 37 | height: 40, 38 | width: 40, 39 | ) 40 | : CachedNetworkImage( 41 | height: 40, 42 | width: 40, 43 | fit: BoxFit.scaleDown, 44 | errorWidget: (context, url, error) => Container( 45 | alignment: Alignment.center, 46 | decoration: const BoxDecoration( 47 | color: Colors.black12, 48 | ), 49 | child: const Icon( 50 | Icons.error, 51 | color: primaryWhite, 52 | ), 53 | ), 54 | imageUrl: '${controller.logoUrl}${e.logoPath}'), 55 | ), 56 | ), 57 | ), 58 | ), 59 | ], 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/views/details/people_details/components/people_flexible_spacebar_option.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/percent_indicator.dart'; 3 | 4 | import '../../../../configs/configs.dart'; 5 | import '../../../../models/peoples/people_model.dart'; 6 | 7 | class PeopleFlexibleSpacebarOptions extends StatelessWidget { 8 | const PeopleFlexibleSpacebarOptions({required this.people, Key? key}) 9 | : super(key: key); 10 | final PeopleModel people; 11 | @override 12 | Widget build(BuildContext context) { 13 | return Padding( 14 | padding: const EdgeInsets.symmetric(horizontal: 14), 15 | child: Row( 16 | children: [ 17 | people.popularity == null 18 | ? const SizedBox.shrink() 19 | : CircularPercentIndicator( 20 | radius: 56, 21 | percent: (people.popularity ?? 0.0).toInt() / 100, 22 | curve: Curves.ease, 23 | animation: true, 24 | animationDuration: 800, 25 | progressColor: primaryblue, 26 | center: Text( 27 | '${((people.popularity ?? 0.0).toInt())}%', 28 | style: const TextStyle( 29 | color: primaryDarkBlue, 30 | fontWeight: FontWeight.w700, 31 | ), 32 | ), 33 | ), 34 | const SizedBox(width: 4), 35 | const Text( 36 | 'User\nScore', 37 | textAlign: TextAlign.center, 38 | style: TextStyle( 39 | fontSize: n - 2, 40 | fontWeight: FontWeight.w700, 41 | color: primaryDarkBlue, 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | 50 | // helpers 51 | class OptionBtn extends StatelessWidget { 52 | const OptionBtn({this.color, this.icon, this.onTap, Key? key}) 53 | : super(key: key); 54 | final IconData? icon; 55 | final void Function()? onTap; 56 | final Color? color; 57 | @override 58 | Widget build(BuildContext context) { 59 | return GestureDetector( 60 | onTap: onTap ?? () {}, 61 | child: Stack( 62 | alignment: AlignmentDirectional.center, 63 | children: [ 64 | Container( 65 | height: 46, 66 | width: 46, 67 | decoration: BoxDecoration( 68 | color: primaryDarkBlue.withOpacity(0.9), 69 | shape: BoxShape.circle, 70 | ), 71 | ), 72 | Icon( 73 | icon ?? Icons.list, 74 | color: color ?? primaryWhite, 75 | ), 76 | ], 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/views/details/components/bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/configs.dart'; 5 | import '../../../controllers/utility_controller.dart'; 6 | 7 | PreferredSizeWidget? bottomTabbarComponent( 8 | {required List tabMenuItems}) { 9 | final _scrollController = ScrollController(); 10 | 11 | return PreferredSize( 12 | preferredSize: Size(MediaQuery.of(Get.context!).size.width, kToolbarHeight), 13 | child: Container( 14 | alignment: Alignment.centerLeft, 15 | child: SingleChildScrollView( 16 | controller: _scrollController, 17 | physics: const BouncingScrollPhysics(), 18 | padding: const EdgeInsets.symmetric(horizontal: 8), 19 | scrollDirection: Axis.horizontal, 20 | child: SizedBox( 21 | height: kToolbarHeight, 22 | child: Row( 23 | children: List.from(tabMenuItems.map((e) { 24 | int index = tabMenuItems.indexOf(e); 25 | return TabbarItem( 26 | index: index, 27 | title: e, 28 | controller: _scrollController, 29 | ); 30 | })), 31 | ), 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | // tabbar item helper zwidget 39 | class TabbarItem extends StatelessWidget { 40 | const TabbarItem( 41 | {required this.index, 42 | required this.title, 43 | required this.controller, 44 | Key? key}) 45 | : super(key: key); 46 | 47 | final int index; 48 | final String title; 49 | final ScrollController controller; 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | final _utilityController = Get.find(); 54 | 55 | return GestureDetector( 56 | onTap: () { 57 | _utilityController.setTabbarIndex(index); 58 | }, 59 | child: Container( 60 | height: 80, 61 | color: Colors.transparent, 62 | alignment: Alignment.center, 63 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), 64 | child: 65 | // 66 | //Obx(() => 67 | GetBuilder( 68 | id: 'bottomTabBar', 69 | init: _utilityController, 70 | builder: (controller) => Text( 71 | title, 72 | style: TextStyle( 73 | fontSize: m - 6, 74 | color: _utilityController.tabbarCurrentIndex == index 75 | ? primaryDarkBlue.withOpacity(0.8) 76 | : primaryDarkBlue.withOpacity(0.5), 77 | fontWeight: FontWeight.w700, 78 | ), 79 | ), 80 | ), 81 | 82 | // ), 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/views/details/season_details/components/season_bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../configs/configs.dart'; 5 | import '../../../../controllers/utility_controller.dart'; 6 | 7 | PreferredSizeWidget? seasonBottomTabbarComponent( 8 | {required List tabMenuItems}) { 9 | final _scrollController = ScrollController(); 10 | 11 | return PreferredSize( 12 | preferredSize: Size(MediaQuery.of(Get.context!).size.width, kToolbarHeight), 13 | child: Container( 14 | alignment: Alignment.centerLeft, 15 | child: SingleChildScrollView( 16 | controller: _scrollController, 17 | physics: const BouncingScrollPhysics(), 18 | padding: const EdgeInsets.symmetric(horizontal: 8), 19 | scrollDirection: Axis.horizontal, 20 | child: SizedBox( 21 | height: kToolbarHeight, 22 | child: Row( 23 | children: List.from(tabMenuItems.map((e) { 24 | int index = tabMenuItems.indexOf(e); 25 | return TabbarItem( 26 | index: index, 27 | title: e, 28 | controller: _scrollController, 29 | ); 30 | })), 31 | ), 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | // helper tabbar items 39 | class TabbarItem extends StatelessWidget { 40 | TabbarItem( 41 | {required this.index, 42 | required this.title, 43 | required this.controller, 44 | Key? key}) 45 | : super(key: key); 46 | final int index; 47 | final String title; 48 | final ScrollController controller; 49 | 50 | final _utilityController = Get.find(); 51 | @override 52 | Widget build(BuildContext context) { 53 | return GestureDetector( 54 | onTap: () { 55 | _utilityController.setSeasonTabbarIndex(index); 56 | }, 57 | child: Container( 58 | height: 80, 59 | color: Colors.transparent, 60 | alignment: Alignment.center, 61 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), 62 | child: 63 | // 64 | //Obx(() => 65 | GetBuilder( 66 | id: 'seasonBottomTabBar', 67 | init: _utilityController, 68 | builder: (controller) => Text( 69 | title, 70 | style: TextStyle( 71 | fontSize: m - 6, 72 | color: _utilityController.seasonTabbarCurrentIndex == index 73 | ? primaryDarkBlue.withOpacity(0.8) 74 | : primaryDarkBlue.withOpacity(0.5), 75 | fontWeight: FontWeight.w700, 76 | ), 77 | ), 78 | ), 79 | 80 | // ), 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /assets/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/src/views/details/people_details/components/people_bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../configs/configs.dart'; 5 | import '../../../../controllers/utility_controller.dart'; 6 | 7 | PreferredSizeWidget? peopleBottomTabbarComponent( 8 | {required List tabMenuItems}) { 9 | final _scrollController = ScrollController(); 10 | 11 | return PreferredSize( 12 | preferredSize: Size(MediaQuery.of(Get.context!).size.width, kToolbarHeight), 13 | child: Container( 14 | alignment: Alignment.centerLeft, 15 | child: SingleChildScrollView( 16 | controller: _scrollController, 17 | physics: const BouncingScrollPhysics(), 18 | padding: const EdgeInsets.symmetric(horizontal: 8), 19 | scrollDirection: Axis.horizontal, 20 | child: SizedBox( 21 | height: kToolbarHeight, 22 | child: Row( 23 | children: List.from(tabMenuItems.map((e) { 24 | int index = tabMenuItems.indexOf(e); 25 | return TabbarItem( 26 | index: index, 27 | title: e, 28 | controller: _scrollController, 29 | ); 30 | })), 31 | ), 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | // tabbar item helper widget 39 | class TabbarItem extends StatelessWidget { 40 | TabbarItem( 41 | {required this.index, 42 | required this.title, 43 | required this.controller, 44 | Key? key}) 45 | : super(key: key); 46 | final int index; 47 | final String title; 48 | final ScrollController controller; 49 | 50 | final _utilityController = Get.find(); 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return GestureDetector( 55 | onTap: () { 56 | _utilityController.setPeopleTabbarIndex(index); 57 | }, 58 | child: Container( 59 | height: 80, 60 | color: Colors.transparent, 61 | alignment: Alignment.center, 62 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), 63 | child: 64 | // 65 | //Obx(() => 66 | GetBuilder( 67 | id: 'peopleBottomTabBar', 68 | init: _utilityController, 69 | builder: (controller) => Text( 70 | title, 71 | style: TextStyle( 72 | fontSize: m - 6, 73 | color: _utilityController.peopleTabbarCurrentIndex == index 74 | ? primaryDarkBlue.withOpacity(0.8) 75 | : primaryDarkBlue.withOpacity(0.5), 76 | fontWeight: FontWeight.w700, 77 | ), 78 | ), 79 | ), 80 | 81 | // ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/views/watchlist/watchlist_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../configs/configs.dart'; 5 | import '../../controllers/utility_controller.dart'; 6 | import 'components/watchlist_bottom_tabbar.dart'; 7 | import 'tabs/movie_watchlist.dart'; 8 | import 'tabs/tv_watchlist.dart'; 9 | 10 | class WatchlistPage extends StatelessWidget { 11 | WatchlistPage({Key? key}) : super(key: key); 12 | 13 | final _utilityController = Get.find(); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return SingleChildScrollView( 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.start, 20 | children: [ 21 | // header 22 | Container( 23 | padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), 24 | alignment: Alignment.centerLeft, 25 | height: 62, 26 | child: const Text( 27 | 'Watchlist', 28 | textAlign: TextAlign.center, 29 | style: TextStyle( 30 | fontSize: l, 31 | // color: primaryWhite, 32 | color: primaryDarkBlue, 33 | ), 34 | ), 35 | ), 36 | 37 | // tabbar 38 | WatchlistTabbarComponent(tabMenuItems: tabItems), 39 | 40 | // tabs 41 | Obx( 42 | () => tabs[_utilityController.wacthlistTabbarCurrentIndex], 43 | ), 44 | ], 45 | ), 46 | ); 47 | // Scaffold( 48 | // appBar: AppBar( 49 | // elevation: 0.6, 50 | // toolbarHeight: 110, 51 | // title: const Text('Watchlist'), 52 | // bottom: searchTabbarComponent(tabMenuItems: searchTabs), 53 | // actions: [ 54 | // IconButton( 55 | // onPressed: () {}, 56 | // icon: const Icon( 57 | // Icons.grid_view, 58 | // size: 26, 59 | // color: primaryDarkBlue, 60 | // )), 61 | // ], 62 | // ), 63 | // body: SingleChildScrollView( 64 | // child: Column( 65 | // children: [ 66 | // // tabs 67 | // Obx( 68 | // () => _searchController.searchState.value == ViewState.idle 69 | // ? _searchController.searchHistory.isEmpty 70 | // ? emptySearch() 71 | // : searchHistory() 72 | // : tabs[_utilityController.searchTabbarCurrentIndex], 73 | // ), 74 | // ], 75 | // ), 76 | // ), 77 | // ); 78 | } 79 | } 80 | 81 | List tabItems = [ 82 | 'Movies', 83 | 'TV Series', 84 | ]; 85 | 86 | List tabs = [ 87 | MovieWatchlist(), 88 | TvWatchlist(), 89 | ]; 90 | -------------------------------------------------------------------------------- /lib/src/views/home/search/tabs/people_search_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../configs/strings.dart'; 5 | import '../../../../controllers/search_controller.dart'; 6 | import '../../../../global/loading_spinner.dart'; 7 | import '../../../../helpers/widget_builder_helper.dart'; 8 | 9 | class PeopleSearchList extends StatelessWidget { 10 | PeopleSearchList({Key? key}) : super(key: key); 11 | 12 | final _searchController = Get.find(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GetBuilder( 17 | id: 'people_search_result', 18 | init: _searchController, 19 | initState: (_) { 20 | _searchController.setResultType(personString); 21 | }, 22 | builder: (controller) => Obx( 23 | () => WidgetBuilderHelper( 24 | state: _searchController.searchState.value, 25 | onLoadingBuilder: 26 | Center(child: LoadingSpinner().fadingCircleSpinner), 27 | onErrorBuilder: const Center( 28 | child: Text('error while loading data ...'), 29 | ), 30 | onSuccessBuilder: 31 | // ListView.builder( 32 | // shrinkWrap: true, 33 | // physics: const NeverScrollableScrollPhysics(), 34 | // itemCount: _searchController.movieSearchResults.length, 35 | // itemBuilder: (context, index) => Text( 36 | // _searchController.movieSearchResults[index].title ?? 37 | // "title"), 38 | // ) 39 | Column( 40 | children: [ 41 | // MovieList(movies: _searchController.movieSearchResults), 42 | const SizedBox(height: 18), 43 | 44 | ListView.separated( 45 | shrinkWrap: true, 46 | physics: const NeverScrollableScrollPhysics(), 47 | itemCount: _searchController.peopleSearchResults.length, 48 | separatorBuilder: (context, index) => 49 | const SizedBox(height: 12), 50 | itemBuilder: (context, index) => GestureDetector( 51 | onTap: () { 52 | Get.offAllNamed('/people_details'); 53 | }, 54 | child: Text( 55 | _searchController.peopleSearchResults[index].name ?? 56 | "name"), 57 | ), 58 | ), 59 | ], 60 | ), 61 | ), 62 | )); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/views/home/search/components/search_history_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../configs/configs.dart'; 5 | import '../../../../controllers/search_controller.dart'; 6 | 7 | enum SearchHistoryType { movie, tv } 8 | 9 | class SearchHistory extends StatelessWidget { 10 | SearchHistory({ 11 | required this.searchHistory, 12 | required this.type, 13 | Key? key, 14 | }) : super(key: key); 15 | 16 | final List searchHistory; 17 | final SearchHistoryType type; 18 | 19 | final _searchController = Get.find(); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Column( 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | Padding( 27 | padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), 28 | child: Row( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | children: [ 31 | Text( 32 | 'Recent searches', 33 | style: TextStyle( 34 | color: primaryDarkBlue.withOpacity(0.8), 35 | fontSize: n, 36 | ), 37 | ), 38 | GestureDetector( 39 | onTap: () { 40 | type == SearchHistoryType.movie 41 | ? _searchController.clearSearchHistoryMovie() 42 | : _searchController.clearSearchHistoryTv(); 43 | }, 44 | child: const Text( 45 | 'Clear all', 46 | style: TextStyle( 47 | color: primaryblue, 48 | fontSize: n, 49 | ), 50 | ), 51 | ), 52 | ], 53 | ), 54 | ), 55 | ListView.builder( 56 | shrinkWrap: true, 57 | reverse: true, 58 | physics: const NeverScrollableScrollPhysics(), 59 | itemCount: searchHistory.length, 60 | itemBuilder: (BuildContext context, int index) { 61 | return ListTile( 62 | onTap: () { 63 | _searchController.search( 64 | query: searchHistory[index], 65 | resultType: _searchController.resultType.value); 66 | }, 67 | title: Text( 68 | searchHistory[index], 69 | style: TextStyle( 70 | color: primaryDarkBlue.withOpacity(0.8), 71 | ), 72 | ), 73 | leading: const Icon(Icons.history), 74 | trailing: const Icon( 75 | Icons.launch, 76 | size: 18, 77 | color: primaryblue, 78 | ), 79 | ); 80 | }, 81 | ), 82 | ], 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/controllers/season_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../service_locator.dart'; 4 | import '../models/details/season_details_model.dart'; 5 | import '../services/season_service.dart'; 6 | import 'base_controller.dart'; 7 | 8 | class SeasonController extends BaseController { 9 | final service = sl(); 10 | 11 | var seasonModel = SeasonModel().obs; 12 | var episodeModel = Episode().obs; 13 | 14 | final _seasonState = ViewState.idle.obs; 15 | final _episodeState = ViewState.idle.obs; 16 | 17 | ViewState get seasonState => _seasonState.value; 18 | ViewState get episodeState => _episodeState.value; 19 | 20 | final _tvId = 0.obs; 21 | final _seasonNo = 1.obs; 22 | final _episodeNo = 1.obs; 23 | 24 | int get tvId => _tvId.value; 25 | int get seasonNo => _seasonNo.value; 26 | int get episodeNo => _episodeNo.value; 27 | 28 | void setTvId(int tvId) => _tvId.value = tvId; 29 | void setSeasonNo(int seasonNo) => _seasonNo.value = seasonNo; 30 | void setEpisodeNo(int episodeNo) => _episodeNo.value = episodeNo; 31 | 32 | // get season details 33 | void getSeasonDetails({ 34 | required String tvId, 35 | required String seasonNo, 36 | }) async { 37 | _seasonState.value = ViewState.busy; 38 | await service 39 | .getSeasonDetails(tvId: tvId, seasonNo: seasonNo) 40 | .then((value) { 41 | seasonModel.value = SeasonModel.fromJson(value); 42 | _seasonState.value = ViewState.retrived; 43 | update(['season_details']); 44 | }); 45 | } 46 | 47 | // get episode details 48 | void getEpisodeDetails({ 49 | required String tvId, 50 | required String seasonNo, 51 | required String episodeNo, 52 | }) async { 53 | _episodeState.value = ViewState.busy; 54 | await service 55 | .getEpisodeDetails(tvId: tvId, seasonNo: seasonNo, episodeNo: episodeNo) 56 | .then((value) { 57 | episodeModel.value = Episode.fromJson(value); 58 | _episodeState.value = ViewState.retrived; 59 | update(['episode_details']); 60 | }); 61 | } 62 | 63 | // rate episode 64 | void rateEpisode({ 65 | required String tvId, 66 | required String seasonNo, 67 | required String episodeNo, 68 | required double ratingValue, 69 | }) async { 70 | await service.rateTvEpisode( 71 | tvId: tvId, 72 | seasonNo: seasonNo, 73 | episodeNo: episodeNo, 74 | ratingValue: ratingValue); 75 | update(['episode_details']); 76 | } 77 | 78 | // rate episode 79 | void deleteEpisodeRating({ 80 | required String tvId, 81 | required String seasonNo, 82 | required String episodeNo, 83 | required double ratingValue, 84 | }) async { 85 | await service.deleteTvEpisodeRating( 86 | tvId: tvId, 87 | seasonNo: seasonNo, 88 | episodeNo: episodeNo, 89 | ratingValue: ratingValue); 90 | update(['episode_details']); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/views/details/tv_details/tabs/reviews/reviews_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import '../../../../../mixins/avatar.dart'; 11 | import '../../../../../models/details/common_details_models.dart'; 12 | import '../../../components/review_builder.dart'; 13 | 14 | class TvReviewTab extends StatelessWidget with AvatarBuilderMixin { 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | TvReviewTab({Key? key}) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return GetBuilder( 23 | id: 'tv_reviews', 24 | init: _detailsController, 25 | initState: (_) { 26 | _detailsController.getOtherDetails( 27 | resultType: tvString, 28 | id: _resultsController.tvId, 29 | appendTo: reviewsString); 30 | }, 31 | builder: (controller) => WidgetBuilderHelper( 32 | state: _detailsController.reviewsState.value, 33 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 34 | onErrorBuilder: const Center( 35 | child: Text('error whlie loading data ...'), 36 | ), 37 | onSuccessBuilder: Column( 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: [ 40 | Padding( 41 | padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 18), 42 | child: Text( 43 | '${_detailsController.reviews.value.results == null ? 0 : _detailsController.reviews.value.results!.length} Reviews', 44 | style: TextStyle( 45 | color: primaryDarkBlue.withOpacity(0.7), 46 | fontSize: n, 47 | ), 48 | ), 49 | ), 50 | ListView.separated( 51 | itemCount: _detailsController.reviews.value.results == null 52 | ? 0 53 | : _detailsController.reviews.value.results!.length, 54 | shrinkWrap: true, 55 | physics: const NeverScrollableScrollPhysics(), 56 | separatorBuilder: (context, index) => const SizedBox(height: 8), 57 | itemBuilder: (context, index) { 58 | ReviewsResult review = 59 | _detailsController.reviews.value.results![index]; 60 | 61 | return ReviewBuilder( 62 | key: UniqueKey(), 63 | review: review, 64 | ); 65 | }, 66 | ), 67 | ], 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/views/details/movie_deatils/tabs/reviews/reviews_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../../configs/configs.dart'; 5 | import '../../../../../configs/strings.dart'; 6 | import '../../../../../controllers/details_controller.dart'; 7 | import '../../../../../controllers/results_controller.dart'; 8 | import '../../../../../global/loading_spinner.dart'; 9 | import '../../../../../helpers/widget_builder_helper.dart'; 10 | import '../../../../../mixins/avatar.dart'; 11 | import '../../../../../models/details/common_details_models.dart'; 12 | import '../../../components/review_builder.dart'; 13 | 14 | class MovieReviewTab extends StatelessWidget with AvatarBuilderMixin { 15 | final _detailsController = Get.find(); 16 | final _resultsController = Get.find(); 17 | 18 | MovieReviewTab({Key? key}) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return GetBuilder( 23 | id: 'movie_reviews', 24 | init: _detailsController, 25 | initState: (_) { 26 | _detailsController.getOtherDetails( 27 | resultType: movieString, 28 | id: _resultsController.movieId, 29 | appendTo: reviewsString); 30 | }, 31 | builder: (controller) => WidgetBuilderHelper( 32 | state: _detailsController.reviewsState.value, 33 | onLoadingBuilder: LoadingSpinner().fadingCircleSpinner, 34 | onErrorBuilder: const Center( 35 | child: Text('error whlie loading data ...'), 36 | ), 37 | onSuccessBuilder: Column( 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: [ 40 | Padding( 41 | padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 18), 42 | child: Text( 43 | '${_detailsController.reviews.value.results == null ? 0 : _detailsController.reviews.value.results!.length} Reviews', 44 | style: TextStyle( 45 | color: primaryDarkBlue.withOpacity(0.7), 46 | fontSize: n, 47 | ), 48 | ), 49 | ), 50 | ListView.separated( 51 | itemCount: _detailsController.reviews.value.results == null 52 | ? 0 53 | : _detailsController.reviews.value.results!.length, 54 | shrinkWrap: true, 55 | physics: const NeverScrollableScrollPhysics(), 56 | separatorBuilder: (context, index) => const SizedBox(height: 8), 57 | itemBuilder: (context, index) { 58 | ReviewsResult review = 59 | _detailsController.reviews.value.results![index]; 60 | 61 | return ReviewBuilder( 62 | key: UniqueKey(), 63 | review: review, 64 | ); 65 | }, 66 | ), 67 | ], 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/views/details/people_details/tabs/about/components/about_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:intl/intl.dart'; 4 | 5 | import '../../../../../../configs/configs.dart'; 6 | import '../../../../../../models/peoples/people_model.dart'; 7 | 8 | // ignore: must_be_immutable 9 | class AboutInfo extends StatelessWidget { 10 | AboutInfo({required this.people, Key? key}) : super(key: key); 11 | final PeopleModel people; 12 | 13 | /// age calculation 14 | String? birthDate; 15 | String? currentDate; 16 | String? dateNow; 17 | String? dateOfBirth; 18 | String? age; 19 | 20 | String? birthday; 21 | @override 22 | Widget build(BuildContext context) { 23 | return GetBuilder(initState: (_) { 24 | birthDate = people.birthday; 25 | currentDate = DateFormat.y().format(DateTime.now()); 26 | dateNow = currentDate!.substring(0, 4); 27 | dateOfBirth = people.birthday == null ? '-' : birthDate!.substring(0, 4); 28 | age = people.birthday == null 29 | ? '-' 30 | : '${int.parse(dateNow!) - int.parse(dateOfBirth!)}'; 31 | birthday = people.birthday == null 32 | ? '-' 33 | : DateFormat.yMMMMd().format(DateTime.parse(people.birthday!)); 34 | }, builder: (_) { 35 | return Padding( 36 | padding: const EdgeInsets.symmetric(vertical: 8), 37 | child: Column( 38 | children: [ 39 | _RowBuilder(prefixText: 'Age', suffixText: age), 40 | _RowBuilder(prefixText: 'Born on', suffixText: birthday), 41 | _RowBuilder( 42 | prefixText: 'From', suffixText: people.placeOfBirth ?? "-"), 43 | ], 44 | ), 45 | ); 46 | }); 47 | } 48 | } 49 | 50 | // helper 51 | class _RowBuilder extends StatelessWidget { 52 | const _RowBuilder({this.prefixText, this.suffixText, Key? key}) 53 | : super(key: key); 54 | final String? prefixText; 55 | final String? suffixText; 56 | @override 57 | Widget build(BuildContext context) { 58 | return Padding( 59 | padding: const EdgeInsets.symmetric(vertical: 2), 60 | child: Row( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | children: [ 63 | Text( 64 | prefixText ?? 'prefix Text', 65 | maxLines: 2, 66 | overflow: TextOverflow.ellipsis, 67 | style: TextStyle( 68 | fontSize: n - 2, 69 | color: primaryDarkBlue.withOpacity(0.5), 70 | ), 71 | ), 72 | const SizedBox(width: 12), 73 | Expanded( 74 | child: Text( 75 | suffixText ?? 'suffix text', 76 | maxLines: 2, 77 | overflow: TextOverflow.ellipsis, 78 | style: TextStyle( 79 | fontSize: n - 2, 80 | color: primaryDarkBlue.withOpacity(0.7), 81 | ), 82 | ), 83 | ), 84 | ], 85 | ), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/models/peoples/people_movie_credits.dart: -------------------------------------------------------------------------------- 1 | class PeopleMovieCreditsModel { 2 | PeopleMovieCreditsModel({ 3 | this.cast, 4 | this.id, 5 | }); 6 | 7 | List? cast; 8 | int? id; 9 | 10 | factory PeopleMovieCreditsModel.fromJson(Map json) => 11 | _$PeopleMovieCreditsModelFromJson(json); 12 | } 13 | 14 | class MovieCast { 15 | MovieCast({ 16 | this.adult, 17 | this.backdropPath, 18 | this.title, 19 | this.genreIds, 20 | this.originalLanguage, 21 | this.originalTitle, 22 | this.posterPath, 23 | this.video, 24 | this.voteAverage, 25 | this.voteCount, 26 | this.overview, 27 | this.releaseDate, 28 | this.id, 29 | this.popularity, 30 | this.character, 31 | this.creditId, 32 | this.order, 33 | }); 34 | 35 | bool? adult; 36 | String? backdropPath; 37 | String? title; 38 | List? genreIds; 39 | String? originalLanguage; 40 | String? originalTitle; 41 | String? posterPath; 42 | bool? video; 43 | double? voteAverage; 44 | int? voteCount; 45 | String? overview; 46 | String? releaseDate; 47 | int? id; 48 | double? popularity; 49 | String? character; 50 | String? creditId; 51 | int? order; 52 | 53 | factory MovieCast.fromJson(Map json) => _$CastFromJson(json); 54 | } 55 | 56 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 57 | /// 58 | /// 59 | /// 60 | /// generated code 61 | /// 62 | /// 63 | /// 64 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 65 | 66 | PeopleMovieCreditsModel _$PeopleMovieCreditsModelFromJson( 67 | Map json) { 68 | return PeopleMovieCreditsModel( 69 | cast: json['cast'] == null 70 | ? null 71 | : List.from(json['cast'].map((e) => MovieCast.fromJson(e))), 72 | id: json['id'] as int?, 73 | ); 74 | } 75 | 76 | MovieCast _$CastFromJson(Map json) { 77 | return MovieCast( 78 | adult: json['adult'] as bool?, 79 | backdropPath: json['backdrop_path'] as String?, 80 | title: json['title'] as String?, 81 | genreIds: 82 | (json['genre_ids'] as List?)?.map((e) => e as int).toList(), 83 | originalLanguage: json['original_language'] as String?, 84 | originalTitle: json['original_title'] as String?, 85 | posterPath: json['poster_path'] as String?, 86 | video: json['video'] as bool?, 87 | voteAverage: (json['vote_average'] as num?)?.toDouble(), 88 | voteCount: json['vote_count'] as int?, 89 | overview: json['overview'] as String?, 90 | releaseDate: 91 | json['release_date'] == null ? null : json['release_date'] as String, 92 | id: json['id'] as int?, 93 | popularity: (json['popularity'] as num?)?.toDouble(), 94 | character: json['character'] as String?, 95 | creditId: json['credit_id'] as String?, 96 | order: json['order'] as int?, 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/models/peoples/people_tv_credits.dart: -------------------------------------------------------------------------------- 1 | class PeopleTvCreditsModel { 2 | PeopleTvCreditsModel({ 3 | this.cast, 4 | this.id, 5 | }); 6 | 7 | List? cast; 8 | int? id; 9 | 10 | factory PeopleTvCreditsModel.fromJson(Map json) => 11 | _$PeopleTvCreditsModelFromJson(json); 12 | } 13 | 14 | class TvCast { 15 | TvCast({ 16 | this.originalName, 17 | this.genreIds, 18 | this.originalLanguage, 19 | this.posterPath, 20 | this.voteAverage, 21 | this.voteCount, 22 | this.overview, 23 | this.id, 24 | this.backdropPath, 25 | this.name, 26 | this.originCountry, 27 | this.firstAirDate, 28 | this.popularity, 29 | this.character, 30 | this.creditId, 31 | this.episodeCount, 32 | }); 33 | 34 | String? originalName; 35 | List? genreIds; 36 | String? originalLanguage; 37 | String? posterPath; 38 | double? voteAverage; 39 | int? voteCount; 40 | String? overview; 41 | int? id; 42 | String? backdropPath; 43 | String? name; 44 | List? originCountry; 45 | String? firstAirDate; 46 | double? popularity; 47 | String? character; 48 | String? creditId; 49 | int? episodeCount; 50 | 51 | factory TvCast.fromJson(Map json) => _$CastFromJson(json); 52 | } 53 | 54 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 55 | /// 56 | /// 57 | /// 58 | /// generated code 59 | /// 60 | /// 61 | /// 62 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 63 | 64 | PeopleTvCreditsModel _$PeopleTvCreditsModelFromJson(Map json) { 65 | return PeopleTvCreditsModel( 66 | cast: json['cast'] == null 67 | ? null 68 | : List.from(json['cast'].map((e) => TvCast.fromJson(e))), 69 | id: json['id'] as int?, 70 | ); 71 | } 72 | 73 | TvCast _$CastFromJson(Map json) { 74 | return TvCast( 75 | originalName: json['original_name'] as String?, 76 | genreIds: 77 | (json['genre_ids'] as List?)?.map((e) => e as int).toList(), 78 | originalLanguage: json['original_language'] as String?, 79 | posterPath: json['poster_path'] as String?, 80 | voteAverage: (json['vote_average'] as num?)?.toDouble(), 81 | voteCount: json['vote_count'] as int?, 82 | overview: json['overview'] as String?, 83 | id: json['id'] as int?, 84 | backdropPath: json['backdrop_path'] as String?, 85 | name: json['name'] as String?, 86 | originCountry: (json['origin_country'] as List?) 87 | ?.map((e) => e as String) 88 | .toList(), 89 | firstAirDate: json['first_air_date'] == null 90 | ? null 91 | : json['first_air_date'] as String, 92 | popularity: (json['popularity'] as num?)?.toDouble(), 93 | character: json['character'] as String?, 94 | creditId: json['credit_id'] as String?, 95 | episodeCount: json['episode_count'] as int?, 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/views/home/search/components/search_bottom_tabbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../configs/configs.dart'; 5 | import '../../../../configs/strings.dart'; 6 | import '../../../../controllers/search_controller.dart'; 7 | import '../../../../controllers/utility_controller.dart'; 8 | 9 | PreferredSizeWidget? searchTabbarComponent( 10 | {required List tabMenuItems}) { 11 | final _scrollController = ScrollController(); 12 | 13 | return PreferredSize( 14 | preferredSize: Size(MediaQuery.of(Get.context!).size.width, kToolbarHeight), 15 | child: Container( 16 | alignment: Alignment.centerLeft, 17 | child: SingleChildScrollView( 18 | controller: _scrollController, 19 | physics: const BouncingScrollPhysics(), 20 | padding: const EdgeInsets.symmetric(horizontal: 8), 21 | scrollDirection: Axis.horizontal, 22 | child: SizedBox( 23 | height: kToolbarHeight, 24 | child: Row( 25 | children: List.from(tabMenuItems.map((e) { 26 | int index = tabMenuItems.indexOf(e); 27 | return tabbarItem( 28 | index: index, 29 | title: e, 30 | controller: _scrollController, 31 | ); 32 | })), 33 | ), 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | 40 | // helper tabbar items 41 | Widget tabbarItem({ 42 | required int index, 43 | required String title, 44 | required ScrollController controller, 45 | }) { 46 | final _utilityController = Get.find(); 47 | final _searchController = Get.find(); 48 | 49 | return GestureDetector( 50 | onTap: () { 51 | switch (index) { 52 | case 0: 53 | _searchController.setResultType(movieString); 54 | // _searchController.movieSearchResults.clear(); 55 | // _searchController.resetSearchState(); 56 | break; 57 | case 1: 58 | _searchController.setResultType(tvString); 59 | // _searchController.tvSearchResults.clear(); 60 | // _searchController.resetSearchState(); 61 | break; 62 | case 2: 63 | _searchController.setResultType(personString); 64 | break; 65 | default: 66 | break; 67 | } 68 | 69 | _utilityController.setSearchTabbarIndex(index); 70 | }, 71 | child: Container( 72 | height: 80, 73 | color: Colors.transparent, 74 | alignment: Alignment.center, 75 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), 76 | child: 77 | // 78 | //Obx(() => 79 | GetBuilder( 80 | id: 'searchTabBar', 81 | init: _utilityController, 82 | builder: (controller) => Text( 83 | title, 84 | style: TextStyle( 85 | fontSize: m - 6, 86 | color: _utilityController.searchTabbarCurrentIndex == index 87 | ? primaryDarkBlue.withOpacity(0.8) 88 | : primaryDarkBlue.withOpacity(0.5), 89 | fontWeight: FontWeight.w700, 90 | ), 91 | ), 92 | ), 93 | 94 | // ), 95 | ), 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/views/watchlist/tabs/movie_watchlist.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../configs/strings.dart'; 5 | import '../../../controllers/account_controller.dart'; 6 | import '../../../controllers/configuration_controller.dart'; 7 | import '../../../global/loading_spinner.dart'; 8 | import '../../../global/movie_thumbnail_card.dart'; 9 | import '../../../helpers/widget_builder_helper.dart'; 10 | 11 | class MovieWatchlist extends StatelessWidget { 12 | MovieWatchlist({Key? key}) : super(key: key); 13 | 14 | final _accountController = Get.find(); 15 | final _configurationController = Get.find(); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GetBuilder( 20 | id: 'movie_watchlist', 21 | init: _accountController, 22 | initState: (_) { 23 | _accountController.getWatchlist(mediaType: moviesString); 24 | print('initialized'); 25 | }, 26 | builder: (controller) => Obx( 27 | () => WidgetBuilderHelper( 28 | state: _accountController.watchlistState.value, 29 | onLoadingBuilder: 30 | Center(child: LoadingSpinner().fadingCircleSpinner), 31 | onErrorBuilder: const Center( 32 | child: Text('error while loading data ...'), 33 | ), 34 | onSuccessBuilder: Column( 35 | children: [ 36 | const SizedBox(height: 28), 37 | GridView.builder( 38 | padding: const EdgeInsets.symmetric(horizontal: 14), 39 | shrinkWrap: true, 40 | physics: const NeverScrollableScrollPhysics(), 41 | itemCount: _accountController.movieWatchlist.length, 42 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 43 | crossAxisCount: 3, 44 | mainAxisSpacing: 12, 45 | crossAxisSpacing: 12, 46 | mainAxisExtent: 186, 47 | ), 48 | itemBuilder: (context, index) => AbsorbPointer( 49 | absorbing: 50 | _accountController.movieWatchlist[index].posterPath == 51 | null 52 | ? true 53 | : false, 54 | child: MovieThumbnailCard( 55 | // onLongPress: () { 56 | // _resultController.setMovieId( 57 | // '${_accountController.movieWatchlist[index].id}'); 58 | // Get.bottomSheet(BottomScrollableSheet( 59 | // movie: _accountController.movieWatchlist[index])); 60 | // }, 61 | padding: const EdgeInsets.all(0), 62 | movie: _accountController.movieWatchlist[index], 63 | imageUrl: 64 | '${_configurationController.posterUrl}${_accountController.movieWatchlist[index].posterPath}', 65 | ), 66 | ), 67 | ), 68 | ], 69 | )), 70 | ), 71 | ); 72 | } 73 | } 74 | --------------------------------------------------------------------------------