├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── zaryabshakir │ │ │ │ └── filmku │ │ │ │ └── filmku │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── images │ ├── icons │ │ ├── default.svg │ │ ├── heart_filled.svg │ │ ├── heart_outlined.svg │ │ ├── help.svg │ │ ├── menu.svg │ │ ├── night.svg │ │ ├── notification.svg │ │ └── sun.svg │ └── movies │ │ ├── drawer.png │ │ └── movie.jpeg └── logo │ └── logo.png ├── flutter_native_splash.yaml ├── fonts ├── merri_weather │ ├── Merriweather-Black.ttf │ └── Merriweather-Bold.ttf └── mulish │ ├── Mulish-Bold.ttf │ └── Mulish-Regular.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── 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-83.5x83.5@2x.png │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ ├── background.png │ │ │ └── darkbackground.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── LaunchImageDark.png │ │ │ ├── LaunchImageDark@2x.png │ │ │ ├── LaunchImageDark@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── app │ ├── app_colors.dart │ ├── app_configs.dart │ ├── app_constants.dart │ ├── app_data.dart │ ├── app_dimens.dart │ ├── app_globals.dart │ ├── app_strings.dart │ ├── app_text_styles.dart │ ├── app_text_theme.dart │ └── app_theme.dart ├── core │ ├── app.dart │ ├── app_env.dart │ └── observers.dart ├── di │ └── Injector.dart ├── features │ ├── bookmarks │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── local │ │ │ │ │ ├── bookmark_local_datasource.dart │ │ │ │ │ └── bookmark_local_datasource_impl.dart │ │ │ │ └── remote │ │ │ │ │ └── bookmark_remote_datasource.dart │ │ │ └── repositories │ │ │ │ └── bookmark_repository_impl.dart │ │ ├── domain │ │ │ ├── repositories │ │ │ │ └── bookmark_repository.dart │ │ │ └── use_cases │ │ │ │ ├── get_bookmarks_use_case.dart │ │ │ │ └── remove_bookmarks_use_case.dart │ │ └── presentation │ │ │ ├── providers │ │ │ ├── bookmark_notifier_provider.dart │ │ │ └── state │ │ │ │ ├── bookmark_notifier.dart │ │ │ │ └── bookmark_state.dart │ │ │ ├── screens │ │ │ └── bookmark_screen.dart │ │ │ └── widgets │ │ │ └── bookmark_card.dart │ ├── home │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── local │ │ │ │ │ ├── home_local_datasource.dart │ │ │ │ │ └── home_local_datasource_impl.dart │ │ │ │ └── remote │ │ │ │ │ ├── home_remote_data_source.dart │ │ │ │ │ └── home_remote_datasource.dart │ │ │ └── repositories │ │ │ │ └── home_repository_impl.dart │ │ ├── domain │ │ │ ├── repositories │ │ │ │ └── home_repository.dart │ │ │ └── use_cases │ │ │ │ ├── fetch_and_cache_genre_use_case.dart │ │ │ │ ├── fetch_and_cache_movies_use_case.dart │ │ │ │ ├── fetch_cached_genre_use_case.dart │ │ │ │ └── fetch_cached_movies_use_case.dart │ │ └── presentation │ │ │ ├── providers │ │ │ ├── home_state_notifier_provider.dart │ │ │ └── state │ │ │ │ ├── genre_notifier.dart │ │ │ │ ├── genre_state.dart │ │ │ │ ├── movie_notifier.dart │ │ │ │ └── movie_state.dart │ │ │ ├── screens │ │ │ ├── home_page.dart │ │ │ └── home_screen.dart │ │ │ └── widgets │ │ │ ├── now_showing_card.dart │ │ │ ├── now_showing_movies.dart │ │ │ ├── popular_card.dart │ │ │ ├── popular_movies.dart │ │ │ └── shimmer │ │ │ ├── now_showing_shimmer.dart │ │ │ └── popular_shimmer.dart │ ├── movie_detail │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── local │ │ │ │ │ ├── movie_detail_local_datasource.dart │ │ │ │ │ └── movie_detail_local_datasource_impl.dart │ │ │ │ └── remote │ │ │ │ │ ├── movie_detail_remote_data_source.dart │ │ │ │ │ └── movie_detail_remote_datasource.dart │ │ │ └── repositories │ │ │ │ └── movie_detail_repository_impl.dart │ │ ├── domain │ │ │ ├── repositories │ │ │ │ └── movie_detail_repository.dart │ │ │ └── use_cases │ │ │ │ ├── add_bookmark_use_case.dart │ │ │ │ ├── get_casts_use_case.dart │ │ │ │ ├── get_movie_details_use_case.dart │ │ │ │ ├── is_bookmark_use_case.dart │ │ │ │ └── remove_bookmark_use_case.dart │ │ └── presentation │ │ │ ├── provider │ │ │ ├── movie_detail_state_notifier.dart │ │ │ └── state │ │ │ │ ├── casts_notifier.dart │ │ │ │ ├── casts_state.dart │ │ │ │ ├── movie_detail_notifier.dart │ │ │ │ └── movie_detail_state.dart │ │ │ ├── screen │ │ │ └── movie_detail_screen.dart │ │ │ └── widget │ │ │ ├── cast_item.dart │ │ │ ├── casts_list.dart │ │ │ ├── movie_detail_body.dart │ │ │ ├── movie_detail_header.dart │ │ │ └── shimmer │ │ │ └── movie_detail_shimmer.dart │ └── notifications │ │ ├── data │ │ ├── datasource │ │ │ ├── local │ │ │ │ ├── notifications_local_datasource.dart │ │ │ │ └── notifications_local_datasource_impl.dart │ │ │ └── remote │ │ │ │ ├── notifications_remote_datasource.dart │ │ │ │ └── notifications_remote_datasource_impl.dart │ │ ├── models │ │ │ ├── notification.dart │ │ │ ├── notification.freezed.dart │ │ │ └── notification.g.dart │ │ └── repository │ │ │ └── notifications_repository_impl.dart │ │ ├── domain │ │ ├── repository │ │ │ └── notifications_repository.dart │ │ └── use_cases │ │ │ ├── clear_all_notifications_use_case.dart │ │ │ └── get_all_notifications_use_case.dart │ │ └── presentation │ │ ├── provider │ │ ├── notification_provider.dart │ │ └── state │ │ │ ├── notification_state.dart │ │ │ └── notifications_notifier.dart │ │ ├── screen │ │ └── notification_screen.dart │ │ └── widget │ │ └── notification_item.dart ├── main.dart ├── models │ ├── cast.dart │ ├── cast.freezed.dart │ ├── cast.g.dart │ ├── casts.dart │ ├── casts.freezed.dart │ ├── casts.g.dart │ ├── domain │ │ ├── movies.dart │ │ ├── movies.freezed.dart │ │ └── movies.g.dart │ ├── genre.dart │ ├── genre.freezed.dart │ ├── genre.g.dart │ ├── genres.dart │ ├── genres.freezed.dart │ ├── genres.g.dart │ ├── message.dart │ ├── movie.dart │ ├── movie.freezed.dart │ ├── movie.g.dart │ ├── movie_detail.dart │ ├── movie_detail.freezed.dart │ ├── movie_detail.g.dart │ └── response │ │ ├── casts_response.dart │ │ ├── genre_response.dart │ │ ├── movie_detail_response.dart │ │ ├── movies_response.dart │ │ └── response.dart ├── routes │ ├── app_router.dart │ └── app_router.g.dart └── shared │ ├── extensions │ └── build_context_extensions.dart │ ├── local │ ├── cache │ │ ├── local_db.dart │ │ └── local_db_impl.dart │ └── shared_prefs │ │ ├── shared_pref.dart │ │ └── shared_pref_impl.dart │ ├── network │ ├── dio_network_service.dart │ ├── exception │ │ └── mixin │ │ │ └── network_handler_mixin.dart │ ├── network_service.dart │ ├── network_values.dart │ └── remote.dart │ ├── provider │ ├── app_theme_provider.dart │ ├── message_queue_provider.dart │ └── state │ │ └── theme_state.dart │ ├── util │ ├── app_exception.dart │ └── message_queue.dart │ └── widgets │ ├── app_bar.dart │ ├── app_bottom_navigation.dart │ ├── app_drawer.dart │ ├── drawer_item.dart │ ├── genre_chip.dart │ ├── message_queue_wrapper.dart │ ├── message_widget.dart │ ├── rating_bar.dart │ ├── see_more.dart │ └── shimmers │ └── skeleton.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── pubspec.lock ├── pubspec.yaml └── screenshots └── screenshots.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | *.env 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | **/ios/Flutter/.last_build_id 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | .packages 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /.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: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 17 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 18 | - platform: android 19 | create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 20 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 21 | - platform: ios 22 | create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 23 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 24 | - platform: linux 25 | create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 26 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zaryab Shakir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | 13 | analyzer: 14 | exclude: 15 | - "**/*.g.dart" 16 | - "**/*.freezed.dart" 17 | errors: 18 | invalid_annotation_target: ignore 19 | 20 | linter: 21 | # The lint rules applied to this project can be customized in the 22 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 23 | # included above or to enable additional rules. A list of all available lints 24 | # and their documentation is published at https://dart.dev/lints. 25 | # 26 | # Instead of disabling a lint rule for the entire project in the 27 | # section below, it can also be suppressed for a single line of code 28 | # or a specific dart file by using the `// ignore: name_of_lint` and 29 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 30 | # producing the lint. 31 | rules: 32 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 33 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 34 | 35 | # Additional information about this file can be found at 36 | # https://dart.dev/guides/language/analysis-options 37 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.zaryabshakir.filmku.filmku" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.zaryabshakir.filmku.filmku" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/zaryabshakir/filmku/filmku/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zaryabshakir.filmku.filmku 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | plugins { 14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 15 | } 16 | } 17 | 18 | include ":app" 19 | 20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" 21 | -------------------------------------------------------------------------------- /assets/images/icons/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 13 | 16 | 20 | 24 | 27 | 30 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /assets/images/icons/heart_filled.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icons/heart_outlined.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icons/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/images/icons/night.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/images/icons/notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/icons/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /assets/images/movies/drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/assets/images/movies/drawer.png -------------------------------------------------------------------------------- /assets/images/movies/movie.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/assets/images/movies/movie.jpeg -------------------------------------------------------------------------------- /assets/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/assets/logo/logo.png -------------------------------------------------------------------------------- /flutter_native_splash.yaml: -------------------------------------------------------------------------------- 1 | flutter_native_splash: 2 | color: "#ffffff" 3 | image: assets/logo/logo.png 4 | color_dark: "#121212" 5 | image_dark: assets/logo/logo.png 6 | 7 | android_12: 8 | image: assets/logo/logo.png 9 | icon_background_color: "#ffffff" 10 | image_dark: assets/logo/logo.png 11 | icon_background_color_dark: "#121212" 12 | 13 | web: false 14 | -------------------------------------------------------------------------------- /fonts/merri_weather/Merriweather-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/fonts/merri_weather/Merriweather-Black.ttf -------------------------------------------------------------------------------- /fonts/merri_weather/Merriweather-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/fonts/merri_weather/Merriweather-Bold.ttf -------------------------------------------------------------------------------- /fonts/mulish/Mulish-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/fonts/mulish/Mulish-Bold.ttf -------------------------------------------------------------------------------- /fonts/mulish/Mulish-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/fonts/mulish/Mulish-Regular.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.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.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/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/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "LaunchImageDark.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "LaunchImage@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "LaunchImageDark@2x.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "LaunchImage@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "LaunchImageDark@3x.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Filmku 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | filmku 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | UIStatusBarHidden 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class AppColors { 4 | static const Color primaryLight = Color(0xFF110E47); 5 | static const Color secondaryLight = Color(0xFF9C9C9C); 6 | static const Color error = Color(0xFFB00020); 7 | static const Color ratingIconColor = Color(0xFFFFC319); 8 | static const Color white = Color(0xffffffff); 9 | static const Color chipColorLight = Color(0xFFDBE3FF); 10 | static const Color chipTextLight = Color(0xFF88A4E8); 11 | static const Color drawerShadowLight = Color(0xffE0E0E0); 12 | static const Color bottomSheetShadowLight = Color(0xffE0E0E0); 13 | 14 | static const Color primaryDark = Color(0xFF5E5A75); 15 | static const Color secondaryDark = Color(0xFF6E6E6E); 16 | static const Color backgroundDark = Color(0xff121212); 17 | static const Color bottomSheetDark = Color(0xff0E0E0E); 18 | static const Color bottomSheetShadowDark = Color(0xff1E1E1E); 19 | static const Color drawerDark = Color(0xff1A1A1A); 20 | static const Color drawerShadowDark = Color(0xff2A2A2A); 21 | 22 | static const Color chipColorDark = Color(0xFF8A96AB); 23 | static const Color chipTextDark = Color(0xFFAEC6F6); 24 | static const Color updateNotificationColorLight = Color(0xFF4CAF50); // Green color 25 | static const Color updateNotificationColorDark = Color(0xFF00796B); // Dark green color 26 | static const Color deleteNotificationColorLight = Color(0xFFE57373); // Red color 27 | static const Color deleteNotificationColorDark = Color(0xFFB71C1C); // Dark red color 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/app_configs.dart: -------------------------------------------------------------------------------- 1 | class AppConfigs { 2 | // static const String baseUrl = 'https://api.themoviedb.org/3/'; 3 | 4 | static String preMovieBackdrop(String path) => 5 | 'https://image.tmdb.org/t/p/w780$path'; 6 | 7 | static String preMoviePoster(String path) => 8 | 'https://image.tmdb.org/t/p/w500$path'; 9 | 10 | static String preCastProfilePath(String path) => 11 | 'https://image.tmdb.org/t/p/w185$path'; 12 | 13 | static const int shouldCachePages = 1; 14 | // static const String apiKey = '0e7274f05c36db12cbe71d9ab0393d47'; 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/app_constants.dart: -------------------------------------------------------------------------------- 1 | class AppConstants { 2 | static const String NOW_SHOWING = 'now_showing'; 3 | static const String POPULAR = 'popular'; 4 | static const String LENGTH = 'Length'; 5 | static const String LANGUAGE = 'Language'; 6 | static const String RATING = 'Rating'; 7 | static const String DESCRIPTION = 'Description'; 8 | static const String CASTS = 'Casts'; 9 | 10 | //App 11 | static const String CURRENT_THEME = 'theme'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/app_data.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/app/app_data.dart -------------------------------------------------------------------------------- /lib/app/app_dimens.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 2 | 3 | class AppDimens { 4 | static double nowShowingPosterWidth = 143.sp; 5 | static double nowShowingPosterHeight = 212.sp; 6 | static double nowShowingCardHeight = 310.sp; 7 | static double nowShowingCardWidth = 143.sp; 8 | static double popularPosterWidth = 85.sp; 9 | static double popularPosterHeight = 128.sp; 10 | static double castProfileHeight = 70.sp; 11 | static double castProfileWidth = 70.sp; 12 | static double movieDetailBackdropHeight = 200.sp; 13 | 14 | //paddings 15 | static double p2 = 2.sp; 16 | static double p4 = 4.sp; 17 | static double p6 = 6.sp; 18 | static double p8 = 8.sp; 19 | static double p10 = 10.sp; 20 | static double p12 = 12.sp; 21 | static double p14 = 14.sp; 22 | static double p16 = 16.sp; 23 | static double p18 = 18.sp; 24 | static double p20 = 20.sp; 25 | static double p22 = 22.sp; 26 | static double p24 = 24.sp; 27 | static double p26 = 26.sp; 28 | static double p28 = 28.sp; 29 | static double p30 = 30.sp; 30 | static double p32 = 32.sp; 31 | static double p34 = 34.sp; 32 | static double p36 = 36.sp; 33 | static double p38 = 38.sp; 34 | static double p40 = 40.sp; 35 | static double p42 = 42.sp; 36 | static double p48 = 48.sp; 37 | static double p54 = 24.sp; 38 | static double p60 = 60.sp; 39 | static double p64 = 64.sp; 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/app_globals.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | final kTestMode = Platform.environment.containsKey('FLUTTER_TEST'); 4 | -------------------------------------------------------------------------------- /lib/app/app_strings.dart: -------------------------------------------------------------------------------- 1 | class AppStrings { 2 | static const String appName = "FilmKu"; 3 | static const String nowShowing = "Now Showing"; 4 | static const String seeMore = "See More"; 5 | static const String popular = "Popular"; 6 | } 7 | -------------------------------------------------------------------------------- /lib/app/app_text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | class AppTextStyles { 5 | //Merri Weather Font Family 6 | static const String merriWeatherFontFamily = 'MerriWeather'; 7 | 8 | //Mulish Font Family 9 | static const String mulishFontFamily = 'Mulish'; 10 | 11 | //Headings 12 | static TextStyle titleLarge = TextStyle( 13 | fontSize: 20.sp, 14 | fontWeight: FontWeight.w700, 15 | fontFamily: mulishFontFamily, 16 | letterSpacing: 0.4); 17 | 18 | static TextStyle titleMedium = TextStyle( 19 | fontSize: 16.sp, 20 | fontWeight: FontWeight.w900, 21 | fontFamily: merriWeatherFontFamily, 22 | letterSpacing: 0.32); 23 | static TextStyle titleSmall = TextStyle( 24 | fontSize: 16.sp, 25 | fontWeight: FontWeight.w700, 26 | fontFamily: mulishFontFamily, 27 | letterSpacing: 0.28); 28 | 29 | //Body 30 | static const TextStyle bodyLarge = TextStyle( 31 | fontSize: 16, 32 | fontWeight: FontWeight.w900, 33 | fontFamily: merriWeatherFontFamily, 34 | letterSpacing: 0.02); 35 | static TextStyle bodyMedium = TextStyle( 36 | fontSize: 14.sp, 37 | fontWeight: FontWeight.w400, 38 | fontFamily: mulishFontFamily, 39 | letterSpacing: 0.24); 40 | 41 | static TextStyle bodySmall = TextStyle( 42 | fontSize: 14.sp, 43 | fontWeight: FontWeight.w400, 44 | fontFamily: mulishFontFamily, 45 | letterSpacing: 0.24); 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/app_text_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/app/app_text_styles.dart'; 3 | 4 | import 'app_colors.dart'; 5 | 6 | class AppTextTheme { 7 | //Simple text theme 8 | static TextTheme get lightTextTheme { 9 | return TextTheme( 10 | titleLarge: AppTextStyles.titleLarge.copyWith(color: AppColors.primaryLight), 11 | titleMedium: AppTextStyles.titleMedium.copyWith(color: AppColors.primaryLight), 12 | titleSmall: AppTextStyles.titleSmall.copyWith(color: AppColors.primaryLight), 13 | bodyLarge: AppTextStyles.bodyLarge.copyWith(color: AppColors.secondaryLight), 14 | bodyMedium: AppTextStyles.bodyMedium.copyWith(color: AppColors.primaryLight), 15 | bodySmall: AppTextStyles.bodySmall.copyWith(color: AppColors.secondaryLight), 16 | ); 17 | } 18 | 19 | //Dark text theme 20 | static TextTheme get darkTextTheme { 21 | return TextTheme( 22 | titleLarge: AppTextStyles.titleLarge.copyWith(color: AppColors.primaryDark), 23 | titleMedium: AppTextStyles.titleMedium.copyWith(color: AppColors.primaryDark), 24 | titleSmall: AppTextStyles.titleSmall.copyWith(color: AppColors.primaryDark), 25 | bodyLarge: AppTextStyles.bodyLarge.copyWith(color: AppColors.secondaryDark), 26 | bodyMedium: AppTextStyles.bodyMedium.copyWith(color: AppColors.primaryDark), 27 | bodySmall: AppTextStyles.bodySmall.copyWith(color: AppColors.secondaryDark), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/app/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/app/app_colors.dart'; 3 | import 'package:filmku/app/app_text_styles.dart'; 4 | import 'package:filmku/app/app_text_theme.dart'; 5 | 6 | class AppTheme { 7 | static ThemeData get lightTheme { 8 | return ThemeData( 9 | brightness: Brightness.light, 10 | primaryColor: AppColors.primaryLight, 11 | colorScheme: const ColorScheme.light( 12 | primary: AppColors.primaryLight, 13 | secondary: AppColors.secondaryLight, 14 | error: AppColors.error, 15 | background: AppColors.white), 16 | scaffoldBackgroundColor: AppColors.white, 17 | primaryTextTheme: AppTextTheme.lightTextTheme, 18 | textTheme: AppTextTheme.lightTextTheme, 19 | appBarTheme: AppBarTheme( 20 | elevation: 0, 21 | backgroundColor: AppColors.white, 22 | titleTextStyle: AppTextStyles.titleMedium), 23 | bottomSheetTheme: const BottomSheetThemeData( 24 | backgroundColor: Colors.white, 25 | shadowColor: AppColors.bottomSheetShadowLight), 26 | drawerTheme: const DrawerThemeData( 27 | backgroundColor: Colors.white, 28 | shadowColor: AppColors.drawerShadowLight), 29 | bottomNavigationBarTheme: const BottomNavigationBarThemeData( 30 | backgroundColor: Colors.white, 31 | )); 32 | } 33 | 34 | static ThemeData get darkTheme { 35 | return ThemeData( 36 | brightness: Brightness.dark, 37 | primaryColor: AppColors.primaryDark, 38 | colorScheme: const ColorScheme.dark( 39 | primary: AppColors.primaryDark, 40 | secondary: AppColors.secondaryDark, 41 | error: AppColors.error, 42 | background: AppColors.backgroundDark), 43 | scaffoldBackgroundColor: AppColors.backgroundDark, 44 | primaryTextTheme: AppTextTheme.darkTextTheme, 45 | textTheme: AppTextTheme.darkTextTheme, 46 | appBarTheme: AppBarTheme( 47 | elevation: 0, 48 | 49 | backgroundColor: AppColors.backgroundDark, 50 | titleTextStyle: AppTextStyles.titleMedium), 51 | bottomSheetTheme: const BottomSheetThemeData( 52 | backgroundColor: AppColors.bottomSheetDark, 53 | shadowColor: AppColors.bottomSheetShadowDark), 54 | drawerTheme: const DrawerThemeData( 55 | backgroundColor: AppColors.drawerDark, 56 | shadowColor: AppColors.drawerShadowDark), 57 | bottomNavigationBarTheme: const BottomNavigationBarThemeData( 58 | backgroundColor: AppColors.backgroundDark, 59 | )); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/core/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:filmku/app/app_strings.dart'; 5 | import 'package:filmku/app/app_theme.dart'; 6 | import 'package:filmku/shared/provider/app_theme_provider.dart'; 7 | 8 | import '../routes/app_router.dart'; 9 | 10 | class MyApp extends ConsumerWidget { 11 | const MyApp({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final goRouter = ref.watch(goRouterProvider); 16 | final themeMode = ref.watch(appThemeProvider); 17 | return ScreenUtilInit( 18 | designSize: const Size(375, 812), 19 | minTextAdapt: true, 20 | splitScreenMode: true, 21 | builder: (BuildContext context, Widget? child) { 22 | return MaterialApp.router( 23 | title: AppStrings.appName, 24 | darkTheme: AppTheme.darkTheme, 25 | theme: AppTheme.lightTheme, 26 | themeMode: themeMode.currentTheme, 27 | routerDelegate: goRouter.routerDelegate, 28 | routeInformationParser: goRouter.routeInformationParser, 29 | routeInformationProvider: goRouter.routeInformationProvider, 30 | debugShowCheckedModeBanner: false, 31 | ); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/app_env.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/core/app_env.dart -------------------------------------------------------------------------------- /lib/core/observers.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | 4 | class Observers extends ProviderObserver { 5 | @override 6 | void didUpdateProvider(ProviderBase provider, Object? previousValue, 7 | Object? newValue, ProviderContainer container) {} 8 | 9 | @override 10 | void didDisposeProvider( 11 | ProviderBase provider, ProviderContainer container) { 12 | super.didDisposeProvider(provider, container); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/bookmarks/data/datasource/local/bookmark_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | abstract class BookmarkLocalDataSource { 6 | Future>> getBookMarkMovies(); 7 | Future removeBookMark(MovieDetail movieDetail); 8 | } 9 | -------------------------------------------------------------------------------- /lib/features/bookmarks/data/datasource/local/bookmark_local_datasource_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/bookmarks/data/datasource/local/bookmark_local_datasource.dart'; 3 | import 'package:filmku/features/notifications/data/models/notification.dart'; 4 | import 'package:filmku/models/movie_detail.dart'; 5 | import 'package:filmku/shared/local/cache/local_db.dart'; 6 | import 'package:filmku/shared/util/app_exception.dart'; 7 | import 'package:isar/isar.dart'; 8 | 9 | class BookmarkLocalDataSourceImpl extends BookmarkLocalDataSource { 10 | LocalDb localDb; 11 | 12 | BookmarkLocalDataSourceImpl({required this.localDb}); 13 | 14 | @override 15 | Future>> getBookMarkMovies() async { 16 | final movies = await localDb.getDb().movieDetails.where().findAll(); 17 | if (movies.isEmpty) { 18 | return Left(AppException( 19 | message: 'No Bookmark available', 20 | statusCode: 0, 21 | identifier: 'bookmark', 22 | which: 'local')); 23 | } else { 24 | return Right(movies); 25 | } 26 | } 27 | 28 | @override 29 | Future removeBookMark(MovieDetail movieDetail) async { 30 | await localDb.getDb().writeTxn(() async{ 31 | await localDb.getDb().movieDetails.delete(movieDetail.isarId); 32 | await localDb.getDb().notificationModels.put(NotificationModel(title: movieDetail.title, message: 'Successfully removed Bookmark', positive: false)); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/bookmarks/data/datasource/remote/bookmark_remote_datasource.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/features/bookmarks/data/datasource/remote/bookmark_remote_datasource.dart -------------------------------------------------------------------------------- /lib/features/bookmarks/data/repositories/bookmark_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/bookmarks/data/datasource/local/bookmark_local_datasource.dart'; 3 | import 'package:filmku/features/bookmarks/domain/repositories/bookmark_repository.dart'; 4 | import 'package:filmku/models/movie_detail.dart'; 5 | import 'package:filmku/shared/util/app_exception.dart'; 6 | 7 | class BookmarkRepositoryImpl extends BookmarkRepository { 8 | BookmarkLocalDataSource bookmarkLocalDataSource; 9 | 10 | BookmarkRepositoryImpl({required this.bookmarkLocalDataSource}); 11 | 12 | @override 13 | Future>> getBookmarks() { 14 | return bookmarkLocalDataSource.getBookMarkMovies(); 15 | } 16 | 17 | @override 18 | Future removeBookmark(MovieDetail movieDetail) { 19 | return bookmarkLocalDataSource.removeBookMark(movieDetail); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/features/bookmarks/domain/repositories/bookmark_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | abstract class BookmarkRepository { 6 | Future>> getBookmarks(); 7 | 8 | Future removeBookmark(MovieDetail movieDetail); 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/bookmarks/domain/use_cases/get_bookmarks_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/bookmarks/domain/repositories/bookmark_repository.dart'; 3 | import 'package:filmku/models/movie_detail.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class GetBookmarksUseCase { 7 | final BookmarkRepository bookmarkRepository; 8 | 9 | GetBookmarksUseCase({required this.bookmarkRepository}); 10 | 11 | Future>> execute() async { 12 | return bookmarkRepository.getBookmarks(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/bookmarks/domain/use_cases/remove_bookmarks_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/bookmarks/domain/repositories/bookmark_repository.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | 4 | class RemoveBookmarkUseCase { 5 | final BookmarkRepository bookmarkRepository; 6 | 7 | RemoveBookmarkUseCase({required this.bookmarkRepository}); 8 | 9 | Future execute(MovieDetail movieDetail) async { 10 | return bookmarkRepository.removeBookmark(movieDetail); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/features/bookmarks/presentation/providers/bookmark_notifier_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:filmku/features/bookmarks/presentation/providers/state/bookmark_notifier.dart'; 3 | import 'package:filmku/features/bookmarks/presentation/providers/state/bookmark_state.dart'; 4 | 5 | final bookmarkNotifierProvider = 6 | AutoDisposeStateNotifierProvider( 7 | (ref) => BookmarkNotifier()); 8 | -------------------------------------------------------------------------------- /lib/features/bookmarks/presentation/providers/state/bookmark_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/bookmarks/domain/use_cases/get_bookmarks_use_case.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/di/Injector.dart'; 5 | import 'package:filmku/features/bookmarks/presentation/providers/state/bookmark_state.dart'; 6 | import 'package:filmku/models/movie_detail.dart'; 7 | import 'package:filmku/shared/util/app_exception.dart'; 8 | 9 | import '../../../../movie_detail/domain/use_cases/remove_bookmark_use_case.dart'; 10 | 11 | class BookmarkNotifier extends StateNotifier { 12 | final GetBookmarksUseCase _getBookmarkUseCase = 13 | injector.get(); 14 | final RemoveBookmarkUseCase _removeBookmarkUseCase = 15 | injector.get(); 16 | 17 | BookmarkNotifier() : super(const BookmarkState.initial()); 18 | 19 | bool get isFetching => state.state != BookmarkConcreteState.loading; 20 | 21 | Future getBookmarks() async { 22 | if (isFetching) { 23 | state = state.copyWith( 24 | state: BookmarkConcreteState.loading, 25 | isLoading: true, 26 | ); 27 | final bookmarks = await _getBookmarkUseCase.execute(); 28 | updateStateFromBookmarksResponse(bookmarks); 29 | } 30 | } 31 | 32 | void removeBookmark(MovieDetail movieDetail) { 33 | _removeBookmarkUseCase.execute(movieDetail); 34 | final bookmarks = state.bookmarks; 35 | bookmarks.remove(movieDetail); 36 | state = state.copyWith( 37 | bookmarks: bookmarks, 38 | hasData: bookmarks.isEmpty ? false : true, 39 | state: bookmarks.isEmpty 40 | ? BookmarkConcreteState.failure 41 | : BookmarkConcreteState.loaded); 42 | } 43 | 44 | void updateStateFromBookmarksResponse( 45 | Either> bookmarks) { 46 | bookmarks.fold((failure) { 47 | state = state.copyWith( 48 | state: BookmarkConcreteState.failure, 49 | message: failure.message, 50 | isLoading: false, 51 | ); 52 | }, (success) { 53 | state = state.copyWith( 54 | bookmarks: success, 55 | message: '', 56 | hasData: true, 57 | isLoading: false, 58 | state: BookmarkConcreteState.loaded); 59 | }); 60 | } 61 | 62 | void resetState() { 63 | state = const BookmarkState.initial(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/features/bookmarks/presentation/providers/state/bookmark_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | 4 | enum BookmarkConcreteState { initial, loading, loaded, failure } 5 | 6 | class BookmarkState extends Equatable { 7 | final List bookmarks; 8 | final bool hasData; 9 | final String message; 10 | final BookmarkConcreteState state; 11 | final bool isLoading; 12 | 13 | const BookmarkState( 14 | {this.bookmarks = const [], 15 | this.hasData = false, 16 | this.message = '', 17 | this.state = BookmarkConcreteState.initial, 18 | this.isLoading = false}); 19 | 20 | const BookmarkState.initial( 21 | {this.bookmarks = const [], 22 | this.hasData = false, 23 | this.message = '', 24 | this.state = BookmarkConcreteState.initial, 25 | this.isLoading = false}); 26 | 27 | BookmarkState copyWith( 28 | {List? bookmarks, 29 | bool? hasData, 30 | String? message, 31 | BookmarkConcreteState? state, 32 | bool? isLoading}) { 33 | return BookmarkState( 34 | bookmarks: bookmarks ?? this.bookmarks, 35 | message: message ?? this.message, 36 | hasData: hasData ?? this.hasData, 37 | state: state ?? this.state, 38 | isLoading: isLoading ?? this.isLoading); 39 | } 40 | 41 | @override 42 | List get props => [bookmarks, hasData, message, state, isLoading]; 43 | } 44 | -------------------------------------------------------------------------------- /lib/features/bookmarks/presentation/screens/bookmark_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:filmku/features/bookmarks/presentation/providers/bookmark_notifier_provider.dart'; 4 | import 'package:filmku/features/bookmarks/presentation/providers/state/bookmark_state.dart'; 5 | import 'package:filmku/features/bookmarks/presentation/widgets/bookmark_card.dart'; 6 | 7 | 8 | class BookmarkScreen extends ConsumerStatefulWidget { 9 | const BookmarkScreen({Key? key}) : super(key: key); 10 | 11 | @override 12 | ConsumerState createState() => _BookmarkScreenState(); 13 | } 14 | 15 | class _BookmarkScreenState extends ConsumerState { 16 | @override 17 | void initState() { 18 | super.initState(); 19 | Future(() { 20 | ref.read(bookmarkNotifierProvider.notifier).getBookmarks(); 21 | }); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final bookmarkNotifier = ref.watch(bookmarkNotifierProvider); 27 | return Scaffold( 28 | body: bookmarkNotifier.state == BookmarkConcreteState.loaded 29 | ? ListView.builder( 30 | scrollDirection: Axis.vertical, 31 | itemCount: bookmarkNotifier.bookmarks.length, 32 | itemBuilder: (context, index) { 33 | return BookmarkCard( 34 | movieDetail: bookmarkNotifier.bookmarks[index]); 35 | } 36 | ) 37 | : bookmarkNotifier.state == BookmarkConcreteState.loading 38 | ? const Center(child: CircularProgressIndicator()) 39 | :const Center(child: Text('No Bookmarks')), 40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/features/home/data/datasource/local/home_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/domain/movies.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | abstract class HomeLocalDataSource { 7 | 8 | Future cacheMovies({required Movies movies}); 9 | 10 | Future cacheGenres({required Genres genres}); 11 | 12 | Future> getCacheMovies({required String type}); 13 | 14 | Future> getGenreCache(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/home/data/datasource/local/home_local_datasource_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/domain/movies.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | import 'package:isar/isar.dart'; 6 | 7 | import 'package:filmku/shared/local/cache/local_db.dart'; 8 | import 'home_local_datasource.dart'; 9 | 10 | class HomeLocalDataSourceImpl extends HomeLocalDataSource { 11 | LocalDb localDb; 12 | 13 | HomeLocalDataSourceImpl({required this.localDb}); 14 | 15 | @override 16 | Future cacheMovies({required Movies movies}) async { 17 | await localDb.getDb().writeTxn(() async { 18 | final cached = await localDb 19 | .getDb() 20 | .movies 21 | .filter() 22 | .typeEqualTo(movies.type) 23 | .pageEqualTo(movies.page) 24 | .findFirst(); 25 | if (cached == null) { 26 | await localDb.getDb().movies.put(movies); 27 | } else { 28 | await localDb.getDb().movies.put(movies.copyWith(id: cached.id)); 29 | } 30 | }); 31 | } 32 | 33 | @override 34 | Future cacheGenres({required Genres genres}) async { 35 | await localDb.getDb().writeTxn(() async { 36 | await localDb.getDb().genres.put(genres); 37 | }); 38 | } 39 | 40 | @override 41 | Future> getGenreCache() async { 42 | final cachedGenres = await localDb.getDb().genres.where().findFirst(); 43 | if (cachedGenres == null) { 44 | return Left(AppException( 45 | message: 'Genre not cached', 46 | statusCode: 0, 47 | identifier: 'genres', 48 | which: 'cache')); 49 | } else { 50 | return Right(cachedGenres); 51 | } 52 | } 53 | 54 | @override 55 | Future> getCacheMovies( 56 | {required String type}) async { 57 | final movies = await localDb 58 | .getDb() 59 | .movies 60 | .filter() 61 | .typeEqualTo(type) 62 | .pageEqualTo(0) // Cache on 0 page 63 | .findFirst(); 64 | if (movies == null) { 65 | return Left(AppException( 66 | message: 'Cache not Found', 67 | statusCode: 0, 68 | identifier: type, 69 | which: 'cache')); 70 | } else { 71 | return Right(movies); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/features/home/data/datasource/remote/home_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/response/genre_response.dart'; 3 | import 'package:filmku/models/response/movies_response.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | abstract class HomeRemoteDataSource { 7 | Future> getMovies( 8 | {required String endPoint, required int page}); 9 | 10 | Future> getGenre(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/home/data/datasource/remote/home_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/data/datasource/remote/home_remote_data_source.dart'; 3 | import 'package:filmku/models/response/genre_response.dart'; 4 | import 'package:filmku/models/response/movies_response.dart'; 5 | import 'package:filmku/shared/network/network_service.dart'; 6 | 7 | import 'package:filmku/shared/network/network_values.dart'; 8 | import 'package:filmku/shared/util/app_exception.dart'; 9 | 10 | class HomeRemoteDataSourceImpl extends HomeRemoteDataSource { 11 | final NetworkService networkService; 12 | 13 | HomeRemoteDataSourceImpl({required this.networkService}); 14 | 15 | @override 16 | Future> getMovies( 17 | {required String endPoint, required int page}) async { 18 | final response = await networkService.get(endPoint, queryParams: { 19 | Params.page: page, 20 | }); 21 | 22 | return response.fold((l) => Left(l), (r) { 23 | final jsonData = r.data; 24 | if (jsonData == null) { 25 | return Left( 26 | AppException( 27 | identifier: endPoint, 28 | statusCode: 0, 29 | message: 'The data is not in the valid format', 30 | which: 'http'), 31 | ); 32 | } 33 | final moviesResponse = 34 | MoviesResponse.fromJson(jsonData, jsonData['results'] ?? []); 35 | return Right(moviesResponse); 36 | }); 37 | } 38 | 39 | @override 40 | Future> getGenre() async { 41 | final response = await networkService.get(EndPoints.genre); 42 | return response.fold((l) => Left(l), (r) { 43 | final jsonData = r.data; 44 | if (jsonData == null) { 45 | return Left(AppException( 46 | identifier: EndPoints.genre, 47 | statusCode: 0, 48 | message: 'The data is not in the valid format', 49 | which: 'http')); 50 | } 51 | final genresResponse = GenreResponse(jsonData['genres'] ?? []); 52 | return Right(genresResponse); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/features/home/data/repositories/home_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/app/app_configs.dart'; 3 | import 'package:filmku/features/home/data/datasource/local/home_local_datasource.dart'; 4 | import 'package:filmku/features/home/data/datasource/remote/home_remote_data_source.dart'; 5 | import 'package:filmku/features/home/domain/repositories/home_repository.dart'; 6 | import 'package:filmku/models/domain/movies.dart'; 7 | import 'package:filmku/models/genres.dart'; 8 | import 'package:filmku/models/movie.dart'; 9 | import 'package:filmku/shared/util/app_exception.dart'; 10 | 11 | import 'package:filmku/models/genre.dart'; 12 | 13 | class HomeRepositoryImpl extends HomeRepository { 14 | final HomeRemoteDataSource homeRemoteDataSource; 15 | final HomeLocalDataSource homeLocalDataSource; 16 | 17 | HomeRepositoryImpl({required this.homeRemoteDataSource, required this.homeLocalDataSource}); 18 | 19 | @override 20 | Future> fetchAndCacheMovies( 21 | {required int page, required String type}) async { 22 | final response = 23 | await homeRemoteDataSource.getMovies(endPoint: type, page: page); 24 | return response.fold((failure) => Left(failure), (success) { 25 | final listMovies = success.results.map((e) => Movie.fromJson(e)).toList(); 26 | final Movies movies = Movies( 27 | movies: listMovies, 28 | page: success.page, 29 | type: type, 30 | totalPages: success.totalPages, 31 | totalResults: success.totalResults); 32 | if (movies.page <= AppConfigs.shouldCachePages) { 33 | homeLocalDataSource.cacheMovies( 34 | movies: movies.copyWith(page: 0, cached: true)); // Cache Page 0 35 | } 36 | return Right(movies); 37 | }); 38 | } 39 | 40 | @override 41 | Future> fetchAndCacheGenres() async { 42 | final response = await homeRemoteDataSource.getGenre(); 43 | return response.fold((failure) => Left(failure), (success) { 44 | final genres = 45 | Genres(genres: success.genres.map((e) => Genre.fromJson(e)).toList()); 46 | homeLocalDataSource.cacheGenres(genres: genres); 47 | return Right(genres); 48 | }); 49 | } 50 | 51 | @override 52 | Future> fetchCachedGenres() { 53 | return homeLocalDataSource.getGenreCache(); 54 | } 55 | 56 | @override 57 | Future> fetchCachedMovies( 58 | {required String type}) { 59 | return homeLocalDataSource.getCacheMovies(type: type); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/features/home/domain/repositories/home_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/domain/movies.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | abstract class HomeRepository { 7 | Future> fetchAndCacheMovies( 8 | {required int page, required String type}); 9 | 10 | Future> fetchAndCacheGenres(); 11 | 12 | Future> fetchCachedGenres(); 13 | 14 | Future> fetchCachedMovies( 15 | {required String type}); 16 | } 17 | -------------------------------------------------------------------------------- /lib/features/home/domain/use_cases/fetch_and_cache_genre_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/repositories/home_repository.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class FetchAndCacheGenreUseCase { 7 | final HomeRepository homeRepository; 8 | 9 | FetchAndCacheGenreUseCase({required this.homeRepository}); 10 | 11 | Future> execute() { 12 | return homeRepository.fetchAndCacheGenres(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/home/domain/use_cases/fetch_and_cache_movies_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/repositories/home_repository.dart'; 3 | import 'package:filmku/models/domain/movies.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class FetchAndCacheMoviesUseCase { 7 | final HomeRepository homeRepository; 8 | 9 | FetchAndCacheMoviesUseCase({required this.homeRepository}); 10 | 11 | Future> execute({required int page,required String type}) { 12 | return homeRepository.fetchAndCacheMovies(page: page ,type: type); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/home/domain/use_cases/fetch_cached_genre_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/repositories/home_repository.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class FetchCacheGenresUseCase { 7 | final HomeRepository homeRepository; 8 | 9 | FetchCacheGenresUseCase({required this.homeRepository}); 10 | 11 | Future> execute() { 12 | return homeRepository.fetchCachedGenres(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/home/domain/use_cases/fetch_cached_movies_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/repositories/home_repository.dart'; 3 | import 'package:filmku/models/domain/movies.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class FetchCachedMoviesUseCase { 7 | final HomeRepository homeRepository; 8 | 9 | FetchCachedMoviesUseCase({required this.homeRepository}); 10 | 11 | Future> execute({required String type}) { 12 | return homeRepository.fetchCachedMovies(type: type); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/home/presentation/providers/home_state_notifier_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:filmku/features/home/presentation/providers/state/genre_notifier.dart'; 3 | import 'package:filmku/features/home/presentation/providers/state/genre_state.dart'; 4 | import 'package:filmku/features/home/presentation/providers/state/movie_notifier.dart'; 5 | import 'package:filmku/features/home/presentation/providers/state/movie_state.dart'; 6 | 7 | final nowShowingMoviesStateNotifier = 8 | AutoDisposeStateNotifierProvider( 9 | (ref) => MovieNotifier()); 10 | 11 | final popularMoviesStateNotifier = 12 | AutoDisposeStateNotifierProvider( 13 | (ref) => MovieNotifier()); 14 | 15 | final genreStateNotifier = 16 | AutoDisposeStateNotifierProvider( 17 | (ref) => GenreNotifier()..getGenres()); -------------------------------------------------------------------------------- /lib/features/home/presentation/providers/state/genre_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/use_cases/fetch_and_cache_genre_use_case.dart'; 3 | import 'package:filmku/features/home/domain/use_cases/fetch_cached_genre_use_case.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:filmku/di/Injector.dart'; 6 | import 'package:filmku/models/genres.dart'; 7 | import 'package:filmku/shared/util/app_exception.dart'; 8 | 9 | import 'genre_state.dart'; 10 | 11 | class GenreNotifier extends StateNotifier { 12 | // final HomeRepository homeRepository = injector.get(); 13 | 14 | final FetchAndCacheGenreUseCase _fetchAndCacheGenreUseCase = 15 | injector.get(); 16 | final FetchCacheGenresUseCase _fetchCacheGenresUseCase = 17 | injector.get(); 18 | 19 | GenreNotifier() : super(const GenreState.initial()); 20 | 21 | bool get isFetching => state.state != GenreConcreteState.loading; 22 | 23 | Future getGenres() async { 24 | if (isFetching) { 25 | state = state.copyWith( 26 | state: GenreConcreteState.loading, 27 | isLoading: true, 28 | ); 29 | final cached = await _fetchCacheGenresUseCase.execute(); 30 | cached.fold((failure) async { 31 | state = state.copyWith(isLoading: true); 32 | final response = await _fetchAndCacheGenreUseCase.execute(); 33 | updateStateFromGenreResponse(response); 34 | }, (success) { 35 | updateStateFromGenreResponse(cached); 36 | }); 37 | } 38 | } 39 | 40 | void updateStateFromGenreResponse(Either response) { 41 | response.fold((failure) { 42 | state = state.copyWith( 43 | state: GenreConcreteState.failure, 44 | message: failure.message, 45 | isLoading: false); 46 | }, (success) { 47 | state = state.copyWith( 48 | genres: success.genres, 49 | hasData: true, 50 | message: success.genres.isEmpty ? 'No genre found' : '', 51 | isLoading: false, 52 | state: GenreConcreteState.loaded); 53 | }); 54 | } 55 | 56 | void resetState() { 57 | state = const GenreState.initial(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/features/home/presentation/providers/state/genre_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import 'package:filmku/models/genre.dart'; 4 | 5 | enum GenreConcreteState { initial, loading, loaded, failure } 6 | 7 | class GenreState extends Equatable { 8 | final List genres; 9 | final bool hasData; 10 | final String message; 11 | final GenreConcreteState state; 12 | final bool isLoading; 13 | 14 | const GenreState( 15 | {this.genres = const [], 16 | this.hasData = false, 17 | this.message = '', 18 | this.state = GenreConcreteState.initial, 19 | this.isLoading = false}); 20 | 21 | const GenreState.initial( 22 | {this.genres = const [], 23 | this.hasData = false, 24 | this.message = '', 25 | this.state = GenreConcreteState.initial, 26 | this.isLoading = false}); 27 | 28 | GenreState copyWith( 29 | {List? genres, 30 | bool? hasData, 31 | String? message, 32 | GenreConcreteState? state, 33 | bool? isLoading}) { 34 | return GenreState( 35 | genres: genres ?? this.genres, 36 | message: message ?? this.message, 37 | hasData: hasData ?? this.hasData, 38 | state: state ?? this.state, 39 | isLoading: isLoading ?? this.isLoading); 40 | } 41 | 42 | @override 43 | List get props => [genres, message, hasData, state, isLoading]; 44 | } 45 | -------------------------------------------------------------------------------- /lib/features/home/presentation/providers/state/movie_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/home/domain/use_cases/fetch_and_cache_movies_use_case.dart'; 3 | import 'package:filmku/features/home/domain/use_cases/fetch_cached_movies_use_case.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:filmku/di/Injector.dart'; 6 | import 'package:filmku/features/home/presentation/providers/state/movie_state.dart'; 7 | import 'package:filmku/models/domain/movies.dart'; 8 | import 'package:filmku/shared/util/app_exception.dart'; 9 | 10 | class MovieNotifier extends StateNotifier { 11 | final FetchAndCacheMoviesUseCase _fetchAndCacheMoviesUseCase = 12 | injector.get(); 13 | final FetchCachedMoviesUseCase _fetchCachedMoviesUseCase = 14 | injector.get(); 15 | 16 | MovieNotifier() : super(const MovieState.initial()); 17 | 18 | bool get isFetching => 19 | state.state != MoviesConcreteState.loading && 20 | state.state != MoviesConcreteState.fetchingMore; 21 | 22 | Future getMovies({required String type}) async { 23 | if (state.state == MoviesConcreteState.initial) { 24 | final movies = await _fetchCachedMoviesUseCase.execute(type: type); 25 | updateStateFromGetMoviesResponse(movies); 26 | } 27 | if (isFetching && state.state != MoviesConcreteState.fetchedAllMovies) { 28 | state = state.copyWith( 29 | state: state.page > 0 30 | ? MoviesConcreteState.fetchingMore 31 | : MoviesConcreteState.loading, 32 | isLoading: true, 33 | ); 34 | final movies = await _fetchAndCacheMoviesUseCase.execute( 35 | page: state.page + 1, type: type); 36 | updateStateFromGetMoviesResponse(movies); 37 | } else { 38 | state = state.copyWith( 39 | state: MoviesConcreteState.fetchedAllMovies, 40 | message: 'No more movies left', 41 | isLoading: false); 42 | } 43 | } 44 | 45 | void updateStateFromGetMoviesResponse(Either response) { 46 | response.fold((failure) { 47 | state = state.copyWith( 48 | state: MoviesConcreteState.failure, 49 | message: failure.message, 50 | isLoading: false); 51 | }, (movies) { 52 | if (state.cache && !movies.cached) { 53 | state = state.copyWith(movies: []); 54 | } 55 | final totalMovies = [...state.movies, ...movies.movies]; 56 | state = state.copyWith( 57 | movies: totalMovies, 58 | state: totalMovies.length == movies.totalResults 59 | ? MoviesConcreteState.fetchedAllMovies 60 | : MoviesConcreteState.loaded, 61 | hasData: true, 62 | cache: movies.cached, 63 | message: totalMovies.isEmpty ? 'No Movies Found' : '', 64 | page: movies.page, 65 | totalPages: movies.totalPages, 66 | isLoading: false); 67 | }); 68 | } 69 | 70 | void resetState() { 71 | state = const MovieState.initial(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/features/home/presentation/providers/state/movie_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import 'package:filmku/models/movie.dart'; 4 | 5 | enum MoviesConcreteState { 6 | initial, 7 | loading, 8 | loaded, 9 | failure, 10 | fetchingMore, 11 | fetchedAllMovies, 12 | } 13 | 14 | class MovieState extends Equatable { 15 | final List movies; 16 | final int page; 17 | final int totalPages; 18 | final int totalResults; 19 | final bool hasData; 20 | final MoviesConcreteState state; 21 | final String message; 22 | final bool isLoading; 23 | final bool cache; 24 | 25 | const MovieState({ 26 | this.movies = const [], 27 | this.page = 0, 28 | this.totalPages = 0, 29 | this.totalResults = 0, 30 | this.hasData = false, 31 | this.state = MoviesConcreteState.initial, 32 | this.message = '', 33 | this.isLoading = false, 34 | this.cache = false, 35 | }); 36 | 37 | const MovieState.initial({ 38 | this.movies = const [], 39 | this.page = 0, 40 | this.totalPages = 0, 41 | this.totalResults = 0, 42 | this.hasData = false, 43 | this.state = MoviesConcreteState.initial, 44 | this.message = '', 45 | this.isLoading = false, 46 | this.cache = false, 47 | }); 48 | 49 | MovieState copyWith({ 50 | List? movies, 51 | int? page, 52 | int? totalPages, 53 | int? totalResults, 54 | bool? hasData, 55 | MoviesConcreteState? state, 56 | String? message, 57 | bool? isLoading, 58 | bool? cache, 59 | }) { 60 | return MovieState( 61 | movies: movies ?? this.movies, 62 | page: page ?? this.page, 63 | totalPages: totalPages ?? this.totalPages, 64 | totalResults: totalResults ?? this.totalResults, 65 | hasData: hasData ?? this.hasData, 66 | state: state ?? this.state, 67 | message: message ?? this.message, 68 | isLoading: isLoading ?? this.isLoading, 69 | cache: cache ?? this.cache); 70 | } 71 | 72 | 73 | @override 74 | String toString() { 75 | return 'MovieState{movies: $movies, page: $page, totalPages: $totalPages, totalResults: $totalResults, hasData: $hasData, state: $state, message: $message, isLoading: $isLoading, cache: $cache}'; 76 | } 77 | 78 | @override 79 | List get props => [movies, page, state, hasData, message,cache]; 80 | } 81 | -------------------------------------------------------------------------------- /lib/features/home/presentation/screens/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:filmku/features/bookmarks/presentation/screens/bookmark_screen.dart'; 5 | import 'package:filmku/features/home/presentation/screens/home_screen.dart'; 6 | import 'package:filmku/shared/widgets/app_bar.dart'; 7 | import 'package:filmku/shared/widgets/app_bottom_navigation.dart'; 8 | import 'package:filmku/shared/widgets/app_drawer.dart'; 9 | 10 | class HomePage extends StatefulWidget { 11 | 12 | const HomePage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _HomePageState(); 16 | } 17 | 18 | class _HomePageState extends State { 19 | int _currentIndex = 0; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: const CustomAppBar(), 25 | drawer: AppDrawer(), 26 | body:homePageBody() , 27 | bottomNavigationBar: AppBottomNavigation(currentIndex: _currentIndex,onTapped: _onTabTapped,), 28 | ); 29 | } 30 | void _onTabTapped(int index) { 31 | setState(() { 32 | log('index $index'); 33 | _currentIndex = index; 34 | }); 35 | } 36 | 37 | Widget homePageBody() { 38 | switch (_currentIndex) { 39 | case 0: 40 | return const HomeScreen(); 41 | case 1: 42 | return const BookmarkScreen(); 43 | default: 44 | return Container(); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /lib/features/home/presentation/widgets/now_showing_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:filmku/app/app_configs.dart'; 5 | import 'package:filmku/app/app_dimens.dart'; 6 | 7 | import 'package:filmku/models/movie.dart'; 8 | 9 | import 'package:filmku/shared/widgets/rating_bar.dart'; 10 | 11 | class NowShowingMovieCard extends StatelessWidget { 12 | final Movie movie; 13 | 14 | const NowShowingMovieCard({Key? key, required this.movie}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SizedBox( 19 | width: AppDimens.nowShowingCardWidth, 20 | child: Padding( 21 | padding: EdgeInsets.only(left: AppDimens.p6), 22 | child: Column( 23 | mainAxisAlignment: MainAxisAlignment.start, 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | Card( 27 | shape: RoundedRectangleBorder( 28 | borderRadius: 29 | BorderRadius.circular(8.sp), // Adjust the radius as needed 30 | ), 31 | elevation: 8.0, 32 | child: ClipRRect( 33 | borderRadius: BorderRadius.circular(8.sp), 34 | child: CachedNetworkImage( 35 | imageUrl: AppConfigs.preMoviePoster(movie.posterPath), 36 | placeholder: (context, url) => 37 | const Center(child: CircularProgressIndicator()), 38 | errorWidget: (context, url, error) => 39 | const Icon(Icons.broken_image), 40 | width: AppDimens.nowShowingPosterWidth, 41 | height: AppDimens.nowShowingPosterHeight, 42 | fit: BoxFit.cover, 43 | ), 44 | ), 45 | ), 46 | Padding( 47 | padding: EdgeInsets.only(top: AppDimens.p6, left: AppDimens.p8), 48 | child: Text( 49 | movie.title, 50 | style: Theme.of(context).textTheme.titleSmall, 51 | maxLines: 2, 52 | ), 53 | ), 54 | Padding( 55 | padding: EdgeInsets.only(top: AppDimens.p2, left: AppDimens.p8), 56 | child: RatingBar(rating: movie.voteAverage), 57 | ) 58 | ], 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/features/home/presentation/widgets/now_showing_movies.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:filmku/app/app_dimens.dart'; 4 | import 'package:filmku/features/home/presentation/providers/home_state_notifier_provider.dart'; 5 | import 'package:filmku/features/home/presentation/widgets/now_showing_card.dart'; 6 | import 'package:filmku/features/home/presentation/widgets/shimmer/now_showing_shimmer.dart'; 7 | import 'package:filmku/routes/app_router.dart'; 8 | import 'package:go_router/go_router.dart'; 9 | 10 | class NowShowingMovies extends ConsumerWidget { 11 | final ScrollController scrollController; 12 | 13 | const NowShowingMovies({super.key, required this.scrollController}); 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | final nowShowingMoviesState = ref.watch(nowShowingMoviesStateNotifier); 18 | return SizedBox( 19 | height: AppDimens.nowShowingCardHeight, 20 | child: nowShowingMoviesState.hasData 21 | ? ListView.builder( 22 | controller: scrollController, 23 | scrollDirection: Axis.horizontal, 24 | itemCount: nowShowingMoviesState.movies.length + 1, 25 | itemBuilder: (context, index) { 26 | if (index < nowShowingMoviesState.movies.length) { 27 | final movie = nowShowingMoviesState.movies[index]; 28 | return GestureDetector( 29 | onTap: () { 30 | context.pushNamed(Routes.movieDetail.name, 31 | extra: movie.id); 32 | }, 33 | child: NowShowingMovieCard(movie: movie)); 34 | } else { 35 | return const Center(child: CircularProgressIndicator()); 36 | } 37 | }, 38 | ) 39 | : const NowShowingMoviesShimmer(), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/home/presentation/widgets/popular_movies.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:filmku/features/home/presentation/providers/home_state_notifier_provider.dart'; 4 | 5 | import 'package:filmku/features/home/presentation/widgets/popular_card.dart'; 6 | import 'package:filmku/features/home/presentation/widgets/shimmer/popular_shimmer.dart'; 7 | import 'package:filmku/routes/app_router.dart'; 8 | import 'package:go_router/go_router.dart'; 9 | 10 | class PopularMovies extends ConsumerWidget { 11 | const PopularMovies({super.key}); 12 | 13 | 14 | @override 15 | Widget build(BuildContext context, WidgetRef ref) { 16 | final popularMoviesState = ref.watch(popularMoviesStateNotifier); 17 | return popularMoviesState.hasData 18 | ? SliverList( 19 | delegate: SliverChildBuilderDelegate((context, index) { 20 | if (index < popularMoviesState.movies.length) { 21 | final movie = popularMoviesState.movies[index]; 22 | return GestureDetector( 23 | onTap: () { 24 | context.pushNamed(Routes.movieDetail.name, 25 | extra: movie.id); 26 | }, 27 | child: PopularMovie(movie: movie)); 28 | } 29 | else { 30 | return const Center(child: CircularProgressIndicator()); 31 | } 32 | }, childCount: popularMoviesState.movies.length + 1)) 33 | : const SliverFillRemaining(hasScrollBody: true,child: PopularMoviesShimmer()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/home/presentation/widgets/shimmer/now_showing_shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/app/app_dimens.dart'; 3 | import 'package:filmku/shared/widgets/shimmers/skeleton.dart'; 4 | import 'package:shimmer/shimmer.dart'; 5 | 6 | class NowShowingMoviesShimmer extends StatelessWidget { 7 | const NowShowingMoviesShimmer({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Shimmer.fromColors( 12 | baseColor: Colors.grey.shade400, 13 | highlightColor: Colors.grey.shade300, 14 | child: ListView.builder( 15 | scrollDirection: Axis.horizontal, 16 | itemCount: 4, 17 | itemBuilder: (context, index) { 18 | return const NowShowingCardShimmer(); 19 | })); 20 | } 21 | } 22 | 23 | class NowShowingCardShimmer extends StatelessWidget { 24 | const NowShowingCardShimmer({Key? key}) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return SizedBox( 29 | height: AppDimens.nowShowingCardHeight, 30 | width: AppDimens.nowShowingCardWidth, 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | // crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | Skeleton( 36 | height: AppDimens.nowShowingPosterHeight, 37 | width: AppDimens.nowShowingPosterWidth, 38 | left: 6, 39 | right: 0, 40 | top: 0, 41 | bottom: 0), 42 | Skeleton( 43 | height: 10, 44 | width: AppDimens.nowShowingPosterWidth, 45 | left: 10, 46 | right: 10, 47 | top: 6, 48 | bottom: 0, 49 | ), 50 | Skeleton( 51 | height: 10, 52 | width: AppDimens.nowShowingPosterWidth, 53 | left: 10, 54 | right: 10, 55 | top: 4, 56 | bottom: 0, 57 | ), 58 | ], 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/features/home/presentation/widgets/shimmer/popular_shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/app/app_dimens.dart'; 3 | import 'package:filmku/shared/widgets/shimmers/skeleton.dart'; 4 | import 'package:shimmer/shimmer.dart'; 5 | 6 | class PopularMoviesShimmer extends StatelessWidget { 7 | const PopularMoviesShimmer({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Shimmer.fromColors( 12 | baseColor: Colors.grey.shade400, 13 | highlightColor: Colors.grey.shade300, 14 | child: ListView.builder( 15 | scrollDirection: Axis.vertical, 16 | itemCount: 3, 17 | itemBuilder: (context, index) { 18 | return const PopularCardShimmer(); 19 | })); 20 | } 21 | } 22 | 23 | class PopularCardShimmer extends StatelessWidget { 24 | const PopularCardShimmer({Key? key}) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return SizedBox( 29 | height: AppDimens.popularPosterHeight, 30 | child: Padding( 31 | padding: EdgeInsets.only(top: AppDimens.p8, left: AppDimens.p18, right: AppDimens.p18), 32 | child: Row( 33 | children:[ 34 | Skeleton( 35 | height: AppDimens.popularPosterHeight, 36 | width: AppDimens.popularPosterWidth, 37 | left: 0, 38 | right: 0, 39 | top: 0, 40 | bottom: 0), 41 | Container( 42 | padding: EdgeInsets.only(left: AppDimens.p12, top: AppDimens.p10), 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | // crossAxisAlignment: CrossAxisAlignment.center, 46 | children: [ 47 | Skeleton( 48 | height: 20, 49 | width: AppDimens.nowShowingPosterWidth, 50 | left: 0, 51 | right: 0, 52 | top: 0, 53 | bottom:0, 54 | ), 55 | Skeleton( 56 | height: 15, 57 | width: AppDimens.nowShowingPosterWidth, 58 | left: 0, 59 | right: 0, 60 | top: 8, 61 | bottom: 0, 62 | ), 63 | Skeleton( 64 | height: 25, 65 | width: AppDimens.nowShowingPosterWidth, 66 | left: 0, 67 | right: 0, 68 | top: 8, 69 | bottom: 0, 70 | ), 71 | ], 72 | ), 73 | ), 74 | ] 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/features/movie_detail/data/datasource/local/movie_detail_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/models/movie_detail.dart'; 2 | 3 | abstract class MovieDetailLocalDataSource { 4 | Future bookmarkMovie(MovieDetail movieDetail); 5 | 6 | Future removeBookmark(MovieDetail movieDetail); 7 | 8 | Future isBookmarked(int id); 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/movie_detail/data/datasource/local/movie_detail_local_datasource_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/movie_detail/data/datasource/local/movie_detail_local_datasource.dart'; 2 | import 'package:filmku/features/notifications/data/models/notification.dart'; 3 | import 'package:filmku/models/movie_detail.dart'; 4 | import 'package:filmku/shared/local/cache/local_db.dart'; 5 | import 'package:isar/isar.dart'; 6 | 7 | class MovieDetailLocalDataSourceImpl extends MovieDetailLocalDataSource { 8 | LocalDb localDb; 9 | 10 | MovieDetailLocalDataSourceImpl({required this.localDb}); 11 | 12 | @override 13 | Future bookmarkMovie(MovieDetail movieDetail) async { 14 | await localDb.getDb().writeTxn(() async { 15 | localDb.getDb().movieDetails.put(movieDetail); 16 | await localDb.getDb().notificationModels.put(NotificationModel(title: movieDetail.title, message: 'Successfully added Bookmark',positive: true)); 17 | }); 18 | return 1; 19 | } 20 | 21 | @override 22 | Future removeBookmark(MovieDetail movieDetail) async { 23 | return await localDb.getDb().writeTxn(()async { 24 | final value= await localDb.getDb().movieDetails.filter().idEqualTo(movieDetail.id).findFirst(); 25 | await localDb.getDb().notificationModels.put(NotificationModel(title: movieDetail.title, message: 'Successfully removed Bookmark',positive: false)); 26 | return await localDb.getDb().movieDetails.delete(value!.isarId); 27 | 28 | }); 29 | } 30 | 31 | @override 32 | Future isBookmarked(int id) async { 33 | final response = await localDb 34 | .getDb() 35 | .movieDetails 36 | .filter() 37 | .idEqualTo(id) 38 | .findFirst(); 39 | if (response == null) { 40 | return false; 41 | } else { 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/features/movie_detail/data/datasource/remote/movie_detail_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/data/datasource/remote/movie_detail_remote_datasource.dart'; 3 | import 'package:filmku/models/movie_detail.dart'; 4 | import 'package:filmku/models/response/casts_response.dart'; 5 | import 'package:filmku/shared/util/app_exception.dart'; 6 | import 'package:filmku/shared/network/network_service.dart'; 7 | import 'package:filmku/shared/network/network_values.dart'; 8 | 9 | 10 | 11 | class MovieDetailRemoteDataSourceImpl extends MovieDetailRemoteDataSource { 12 | final NetworkService networkService; 13 | 14 | MovieDetailRemoteDataSourceImpl({required this.networkService}); 15 | 16 | @override 17 | Future> getMovie( 18 | {required int id}) async { 19 | final response = 20 | await networkService.get(EndPoints.movie(id)); 21 | return response.fold((l) => Left(l), (r) { 22 | final jsonData = r.data; 23 | if (jsonData == null) { 24 | return Left(AppException( 25 | identifier: EndPoints.movie(id), 26 | message: 'The data is not in the valid format', 27 | statusCode: 0, 28 | which: 'http', 29 | )); 30 | } else { 31 | return Right(MovieDetail.fromJson(jsonData)); 32 | } 33 | }); 34 | } 35 | 36 | @override 37 | Future> getCasts( 38 | {required int id}) async { 39 | final response = 40 | await networkService.get(EndPoints.casts(id)); 41 | return response.fold((l) => Left(l), (r) { 42 | final jsonData = r.data; 43 | if (jsonData == null) { 44 | return Left(AppException( 45 | identifier: EndPoints.casts(id), 46 | statusCode: 0, 47 | message: 'The data is not in valid for', 48 | which: 'http')); 49 | } else { 50 | final castsResponse = CastsResponse(jsonData['cast'] ?? []); 51 | return Right(castsResponse); 52 | } 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/features/movie_detail/data/datasource/remote/movie_detail_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | import 'package:filmku/models/response/casts_response.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | abstract class MovieDetailRemoteDataSource { 7 | Future> getMovie({required int id}); 8 | 9 | Future> getCasts({required int id}); 10 | } 11 | -------------------------------------------------------------------------------- /lib/features/movie_detail/data/repositories/movie_detail_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/data/datasource/local/movie_detail_local_datasource.dart'; 3 | import 'package:filmku/features/movie_detail/data/datasource/remote/movie_detail_remote_datasource.dart'; 4 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 5 | import 'package:filmku/models/movie_detail.dart'; 6 | import 'package:filmku/models/response/casts_response.dart'; 7 | 8 | import 'package:filmku/shared/util/app_exception.dart'; 9 | 10 | class MovieDetailRepositoryImpl extends MovieDetailRepository { 11 | final MovieDetailRemoteDataSource movieDetailDataSource; 12 | final MovieDetailLocalDataSource movieDetailLocalDataSource; 13 | 14 | MovieDetailRepositoryImpl( 15 | {required this.movieDetailDataSource, 16 | required this.movieDetailLocalDataSource}); 17 | 18 | @override 19 | Future> getMovie({required int id}) async { 20 | return movieDetailDataSource.getMovie(id: id); 21 | } 22 | 23 | @override 24 | Future> getCasts({required int id}) { 25 | return movieDetailDataSource.getCasts(id: id); 26 | } 27 | 28 | @override 29 | Future bookmarkMovieDetail(MovieDetail movieDetail) { 30 | return movieDetailLocalDataSource.bookmarkMovie(movieDetail); 31 | } 32 | 33 | @override 34 | Future removeBookmark(MovieDetail movieDetail) { 35 | return movieDetailLocalDataSource.removeBookmark(movieDetail); 36 | } 37 | 38 | @override 39 | Future isBookmarked(int id) { 40 | return movieDetailLocalDataSource.isBookmarked(id); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/repositories/movie_detail_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | import 'package:filmku/models/response/casts_response.dart'; 4 | 5 | import 'package:filmku/shared/util/app_exception.dart'; 6 | 7 | abstract class MovieDetailRepository { 8 | Future> getMovie({required int id}); 9 | 10 | Future> getCasts({required int id}); 11 | 12 | Future bookmarkMovieDetail(MovieDetail movieDetail); 13 | 14 | Future removeBookmark(MovieDetail movieDetail); 15 | 16 | Future isBookmarked(int id); 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/use_cases/add_bookmark_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | 4 | class AddBookmarkUseCase { 5 | final MovieDetailRepository movieDetailRepository; 6 | 7 | AddBookmarkUseCase({required this.movieDetailRepository}); 8 | 9 | Future execute(MovieDetail movieDetail) async { 10 | return movieDetailRepository.bookmarkMovieDetail(movieDetail); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/use_cases/get_casts_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 3 | import 'package:filmku/models/response/casts_response.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class GetCastsUseCase { 7 | final MovieDetailRepository movieDetailRepository; 8 | 9 | GetCastsUseCase({required this.movieDetailRepository}); 10 | 11 | Future> execute({required int movieId}) async { 12 | return movieDetailRepository.getCasts(id: movieId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/use_cases/get_movie_details_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 3 | import 'package:filmku/models/movie_detail.dart'; 4 | import 'package:filmku/shared/util/app_exception.dart'; 5 | 6 | class GetMovieDetailsUseCase { 7 | final MovieDetailRepository movieDetailRepository; 8 | 9 | GetMovieDetailsUseCase({required this.movieDetailRepository}); 10 | 11 | Future> execute( 12 | {required int movieId}) async { 13 | return movieDetailRepository.getMovie(id: movieId); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/use_cases/is_bookmark_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 2 | 3 | class IsBookmarkedUseCase { 4 | final MovieDetailRepository movieDetailRepository; 5 | 6 | IsBookmarkedUseCase({required this.movieDetailRepository}); 7 | 8 | Future execute(int movieId) async { 9 | return movieDetailRepository.isBookmarked(movieId); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/features/movie_detail/domain/use_cases/remove_bookmark_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/movie_detail/domain/repositories/movie_detail_repository.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | 4 | class RemoveBookmarkUseCase { 5 | final MovieDetailRepository movieDetailRepository; 6 | 7 | RemoveBookmarkUseCase({required this.movieDetailRepository}); 8 | 9 | Future execute(MovieDetail movieDetail) async { 10 | return movieDetailRepository.removeBookmark(movieDetail); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/provider/movie_detail_state_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:filmku/features/movie_detail/presentation/provider/state/casts_notifier.dart'; 3 | import 'package:filmku/features/movie_detail/presentation/provider/state/casts_state.dart'; 4 | import 'package:filmku/features/movie_detail/presentation/provider/state/movie_detail_notifier.dart'; 5 | import 'package:filmku/features/movie_detail/presentation/provider/state/movie_detail_state.dart'; 6 | 7 | final movieDetailStateNotifier = 8 | AutoDisposeStateNotifierProvider( 9 | (ref) => MovieDetailNotifier()); 10 | 11 | 12 | final castsStateNotifier = 13 | AutoDisposeStateNotifierProvider.family( 14 | (ref,id) => CastsNotifier()..getCasts(id: id)); -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/provider/state/casts_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/domain/use_cases/get_casts_use_case.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/di/Injector.dart'; 5 | import 'package:filmku/features/movie_detail/presentation/provider/state/casts_state.dart'; 6 | import 'package:filmku/models/cast.dart'; 7 | import 'package:filmku/models/response/casts_response.dart'; 8 | import 'package:filmku/shared/util/app_exception.dart'; 9 | 10 | class CastsNotifier extends StateNotifier { 11 | 12 | final GetCastsUseCase _getCastsUseCase = injector.get(); 13 | 14 | 15 | CastsNotifier() : super(const CastsState.initial()); 16 | 17 | bool get isFetching => state.state != CastConcreteState.loading; 18 | 19 | Future getCasts({required int id}) async { 20 | if (isFetching) { 21 | state = state.copyWith( 22 | state: CastConcreteState.loading, 23 | isLoading: true, 24 | ); 25 | final response = await _getCastsUseCase.execute( 26 | movieId: id); 27 | updateStateFromGetCastsResponse(response); 28 | } 29 | } 30 | 31 | void updateStateFromGetCastsResponse( 32 | Either> response) { 33 | response.fold((failure) { 34 | state = state.copyWith( 35 | state: CastConcreteState.failure, 36 | message: failure.message, 37 | hasData: false, 38 | isLoading: false, 39 | ); 40 | }, (results) { 41 | final casts = results.casts.map((e) => Cast.fromJson(e)).toList(); 42 | state = state.copyWith( 43 | state: CastConcreteState.loaded, 44 | casts: casts, 45 | hasData: false, 46 | message: casts.isEmpty ? 'no casts found' : '', 47 | isLoading: false); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/provider/state/casts_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:filmku/models/cast.dart'; 3 | 4 | enum CastConcreteState { initial, loading, loaded, failure } 5 | 6 | class CastsState extends Equatable { 7 | final int id; 8 | final List casts; 9 | final bool hasData; 10 | final String message; 11 | final bool isLoading; 12 | final CastConcreteState state; 13 | 14 | const CastsState( 15 | {this.id = 0, 16 | this.casts = const [], 17 | this.hasData = false, 18 | this.message = '', 19 | this.isLoading = false, 20 | this.state = CastConcreteState.initial}); 21 | 22 | const CastsState.initial( 23 | {this.id = 0, 24 | this.casts = const [], 25 | this.hasData = false, 26 | this.message = '', 27 | this.isLoading = false, 28 | this.state = CastConcreteState.initial}); 29 | 30 | CastsState copyWith( 31 | {int? id, 32 | List? casts, 33 | bool? hasData, 34 | String? message, 35 | bool? isLoading, 36 | CastConcreteState? state}) { 37 | return CastsState( 38 | id: id ?? this.id, 39 | casts: casts ?? this.casts, 40 | hasData: hasData ?? this.hasData, 41 | isLoading: isLoading ?? this.isLoading, 42 | state: state ?? this.state); 43 | } 44 | 45 | @override 46 | List get props => [id, casts, hasData, isLoading, state]; 47 | } 48 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/provider/state/movie_detail_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/movie_detail/domain/use_cases/add_bookmark_use_case.dart'; 3 | import 'package:filmku/features/movie_detail/domain/use_cases/get_movie_details_use_case.dart'; 4 | import 'package:filmku/features/movie_detail/domain/use_cases/is_bookmark_use_case.dart'; 5 | import 'package:filmku/features/movie_detail/domain/use_cases/remove_bookmark_use_case.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:filmku/di/Injector.dart'; 8 | import 'package:filmku/features/movie_detail/presentation/provider/state/movie_detail_state.dart'; 9 | import 'package:filmku/models/movie_detail.dart'; 10 | import 'package:filmku/shared/util/app_exception.dart'; 11 | 12 | class MovieDetailNotifier extends StateNotifier { 13 | final AddBookmarkUseCase _addBookmarkUseCase = 14 | injector.get(); 15 | final RemoveBookmarkUseCase _removeBookmarkUseCase = 16 | injector.get(); 17 | final GetMovieDetailsUseCase _getMovieDetailsUseCase = 18 | injector.get(); 19 | final IsBookmarkedUseCase _isBookmarkedUseCase = 20 | injector.get(); 21 | 22 | MovieDetailNotifier() 23 | : super(const MovieDetailState(movieDetail: MovieDetail())); 24 | 25 | bool get isFetching => state.state != MovieDetailConcreteState.loading; 26 | 27 | Future bookmarkMovieDetail(MovieDetail movieDetail) async { 28 | final response = await _addBookmarkUseCase.execute(movieDetail); 29 | state = state.copyWith(isBookmarked: true); 30 | } 31 | 32 | Future removeBookmark(MovieDetail movieDetail) async { 33 | final response = await _removeBookmarkUseCase.execute(movieDetail); 34 | state = state.copyWith(isBookmarked: false); 35 | } 36 | 37 | Future checkIfBookMarked(int id) async { 38 | final isBookmarked = await _isBookmarkedUseCase.execute(id); 39 | state = state.copyWith(isBookmarked: isBookmarked); 40 | } 41 | 42 | Future getMovie({required int id}) async { 43 | if (isFetching) { 44 | state = state.copyWith( 45 | state: MovieDetailConcreteState.loading, 46 | isLoading: true, 47 | ); 48 | final response = await _getMovieDetailsUseCase.execute(movieId: id); 49 | updateStateFromMovieResponse(response); 50 | } 51 | } 52 | 53 | void updateStateFromMovieResponse( 54 | Either response) { 55 | response.fold((failure) { 56 | state = state.copyWith( 57 | state: MovieDetailConcreteState.failure, 58 | message: failure.message, 59 | hasData: false, 60 | isLoading: false); 61 | }, (success) { 62 | state = state.copyWith( 63 | state: MovieDetailConcreteState.loaded, 64 | movieDetail: success, 65 | hasData: true, 66 | isLoading: false, 67 | message: '', 68 | ); 69 | }); 70 | } 71 | 72 | void resetState() { 73 | state = const MovieDetailState.initial(movieDetail: MovieDetail()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/provider/state/movie_detail_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:filmku/models/movie_detail.dart'; 3 | 4 | enum MovieDetailConcreteState { 5 | initial, 6 | loading, 7 | loaded, 8 | failure, 9 | } 10 | 11 | class MovieDetailState extends Equatable { 12 | final int id; 13 | final MovieDetail movieDetail; 14 | final bool isBookmarked; 15 | final bool hasData; 16 | final String message; 17 | final bool isLoading; 18 | final MovieDetailConcreteState state; 19 | 20 | const MovieDetailState( 21 | {this.id = 0, 22 | required this.movieDetail, 23 | this.isBookmarked = false, 24 | this.hasData = false, 25 | this.message = '', 26 | this.isLoading = false, 27 | this.state = MovieDetailConcreteState.initial}); 28 | 29 | const MovieDetailState.initial( 30 | {this.id = 0, 31 | required this.movieDetail, 32 | this.isBookmarked = false, 33 | this.hasData = false, 34 | this.message = '', 35 | this.isLoading = false, 36 | this.state = MovieDetailConcreteState.initial}); 37 | 38 | MovieDetailState copyWith({ 39 | int? id, 40 | MovieDetail? movieDetail, 41 | bool? hasData, 42 | String? message, 43 | bool? isLoading, 44 | MovieDetailConcreteState? state, 45 | bool? isBookmarked, 46 | }) { 47 | return MovieDetailState( 48 | id: id ?? this.id, 49 | movieDetail: movieDetail ?? this.movieDetail, 50 | hasData: hasData ?? this.hasData, 51 | message: message ?? this.message, 52 | isLoading: isLoading ?? this.isLoading, 53 | isBookmarked: isBookmarked ?? this.isBookmarked, 54 | state: state ?? this.state); 55 | } 56 | 57 | @override 58 | List get props => 59 | [id, movieDetail, hasData, message, isLoading, state, isBookmarked]; 60 | } 61 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/screen/movie_detail_screen.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/features/movie_detail/presentation/provider/movie_detail_state_notifier.dart'; 5 | import 'package:filmku/features/movie_detail/presentation/provider/state/movie_detail_state.dart'; 6 | import 'package:filmku/features/movie_detail/presentation/widget/movie_detail_body.dart'; 7 | import 'package:filmku/features/movie_detail/presentation/widget/movie_detail_header.dart'; 8 | 9 | 10 | class MovieDetailScreen extends ConsumerStatefulWidget { 11 | final int movieId; 12 | 13 | const MovieDetailScreen({super.key, required this.movieId}); 14 | 15 | @override 16 | ConsumerState createState() => 17 | _MovieDetailScreenState(); 18 | } 19 | 20 | class _MovieDetailScreenState extends ConsumerState { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | Future(() { 25 | ref.read(movieDetailStateNotifier.notifier).getMovie(id: widget.movieId); 26 | ref.read(movieDetailStateNotifier.notifier).checkIfBookMarked(widget.movieId); 27 | }); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final notifier = ref.watch(movieDetailStateNotifier); 33 | return Scaffold( 34 | body: notifier.state == MovieDetailConcreteState.loading 35 | ? const Center(child: CircularProgressIndicator()) 36 | : CustomScrollView(slivers: [ 37 | MovieDetailHeader(movieDetail: notifier.movieDetail), 38 | MovieDetailBody(movieDetail: notifier.movieDetail) 39 | ])); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/widget/cast_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:filmku/app/app_configs.dart'; 5 | import 'package:filmku/app/app_dimens.dart'; 6 | import 'package:filmku/models/cast.dart'; 7 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 8 | 9 | class CastItem extends StatelessWidget { 10 | final Cast cast; 11 | 12 | const CastItem({Key? key, required this.cast}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SizedBox( 17 | width: AppDimens.castProfileWidth, 18 | child: Padding( 19 | padding: EdgeInsets.only(left: AppDimens.p8) , 20 | child: Column( 21 | mainAxisAlignment: MainAxisAlignment.start, 22 | children: [ 23 | ClipRRect( 24 | borderRadius: BorderRadius.circular(8.sp), 25 | child: CachedNetworkImage( 26 | imageUrl: AppConfigs.preCastProfilePath(cast.profilePath), 27 | placeholder: (context, url) => const Center(child: CircularProgressIndicator()), 28 | errorWidget: (context, url,error) => const Icon(Icons.broken_image), 29 | height: AppDimens.castProfileHeight, 30 | width: AppDimens.castProfileWidth, 31 | fit: BoxFit.cover, 32 | ), 33 | ), 34 | SizedBox( 35 | height: 4.sp, 36 | ), 37 | Text( 38 | cast.originalName, 39 | style: context.textTheme.bodyMedium, 40 | maxLines: 2, 41 | overflow: TextOverflow.ellipsis, 42 | ), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/widget/casts_list.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/features/movie_detail/presentation/provider/movie_detail_state_notifier.dart'; 5 | import 'package:filmku/features/movie_detail/presentation/provider/state/casts_state.dart'; 6 | import 'package:filmku/features/movie_detail/presentation/widget/cast_item.dart'; 7 | 8 | class CastsList extends ConsumerWidget { 9 | final int id; 10 | 11 | const CastsList({Key? key, required this.id}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final castsNotifier = ref.watch(castsStateNotifier(id)); 16 | return castsNotifier.state == CastConcreteState.loading 17 | ? const Center(child: CircularProgressIndicator()) 18 | : ListView.builder( 19 | itemCount: castsNotifier.casts.length, 20 | scrollDirection: Axis.horizontal, 21 | itemBuilder: (context, index) { 22 | return CastItem( 23 | cast: castsNotifier.casts[index], 24 | ); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/widget/movie_detail_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:filmku/app/app_colors.dart'; 4 | import 'package:filmku/app/app_configs.dart'; 5 | import 'package:filmku/app/app_dimens.dart'; 6 | import 'package:filmku/models/movie_detail.dart'; 7 | 8 | class MovieDetailHeader extends StatelessWidget { 9 | final MovieDetail movieDetail; 10 | 11 | const MovieDetailHeader({Key? key, required this.movieDetail}) 12 | : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SliverAppBar( 17 | expandedHeight: AppDimens.movieDetailBackdropHeight, 18 | leading: GestureDetector( 19 | onTap: () { 20 | Navigator.pop(context); 21 | }, 22 | child: const Icon( 23 | Icons.arrow_back_ios, 24 | color: AppColors.primaryLight, 25 | ), 26 | ), 27 | actions: [ 28 | IconButton( 29 | onPressed: () { 30 | // context.router 31 | }, 32 | icon: const Icon(Icons.more_horiz), 33 | color: AppColors.primaryLight, 34 | ) 35 | ], 36 | stretch: true, 37 | pinned: false, 38 | floating: true, 39 | flexibleSpace: FlexibleSpaceBar( 40 | stretchModes: const [ 41 | StretchMode.zoomBackground, 42 | StretchMode.blurBackground 43 | ], 44 | background: CachedNetworkImage( 45 | imageUrl: AppConfigs.preMovieBackdrop(movieDetail.backdropPath), 46 | placeholder: (context, url) => 47 | const Center(child: CircularProgressIndicator()), 48 | errorWidget: (context, url, error) => const Icon(Icons.broken_image), 49 | fit: BoxFit.cover, 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/features/movie_detail/presentation/widget/shimmer/movie_detail_shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | class MovieDetailShimmer extends StatelessWidget { 5 | const MovieDetailShimmer({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Shimmer.fromColors( 10 | baseColor: Colors.grey.shade400, 11 | highlightColor: Colors.grey.shade300, 12 | child: Column( 13 | 14 | children: [ 15 | ], 16 | )); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/notifications/data/datasource/local/notifications_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/notifications/data/models/notification.dart'; 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | abstract class NotificationsLocalDataSource { 6 | Future>> getNotifications(); 7 | 8 | Future clearNotifications(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/notifications/data/datasource/local/notifications_local_datasource_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'package:filmku/features/notifications/data/models/notification.dart'; 4 | import 'package:filmku/shared/local/cache/local_db.dart'; 5 | 6 | import 'package:filmku/shared/util/app_exception.dart'; 7 | import 'package:isar/isar.dart'; 8 | 9 | import 'notifications_local_datasource.dart'; 10 | 11 | class NotificationsLocalDataSourceImpl extends NotificationsLocalDataSource { 12 | final LocalDb localDb; 13 | 14 | NotificationsLocalDataSourceImpl({required this.localDb}); 15 | 16 | @override 17 | Future clearNotifications() async { 18 | await localDb.getDb().writeTxn(() async { 19 | await localDb.getDb().notificationModels.clear(); 20 | }); 21 | } 22 | 23 | @override 24 | Future>> 25 | getNotifications() async { 26 | final response = await localDb.getDb().notificationModels.where(sort: Sort.asc).findAll(); 27 | if (response.isEmpty) { 28 | return Left(AppException( 29 | message: 'No Notification available', 30 | statusCode: 0, 31 | identifier: 'notification', 32 | which: 'local')); 33 | } else { 34 | return Right(response); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/features/notifications/data/datasource/remote/notifications_remote_datasource.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/features/notifications/data/datasource/remote/notifications_remote_datasource.dart -------------------------------------------------------------------------------- /lib/features/notifications/data/datasource/remote/notifications_remote_datasource_impl.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/features/notifications/data/datasource/remote/notifications_remote_datasource_impl.dart -------------------------------------------------------------------------------- /lib/features/notifications/data/models/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:isar/isar.dart'; 3 | part 'notification.freezed.dart'; 4 | part 'notification.g.dart'; 5 | @freezed 6 | @Collection(ignore: {'copyWith'}) 7 | class NotificationModel with _$NotificationModel{ 8 | const factory NotificationModel({ 9 | @Default(Isar.autoIncrement) int id, 10 | @Default('') String title, 11 | @Default('') String message, 12 | @Default(false) bool positive, 13 | 14 | })= _NotificationModel; 15 | 16 | @override 17 | Id get id; 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/notifications/data/repository/notifications_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/notifications/data/datasource/local/notifications_local_datasource.dart'; 3 | import 'package:filmku/features/notifications/data/models/notification.dart'; 4 | import 'package:filmku/features/notifications/domain/repository/notifications_repository.dart'; 5 | import 'package:filmku/shared/util/app_exception.dart'; 6 | 7 | class NotificationRepositoryImpl extends NotificationRepository { 8 | NotificationsLocalDataSource notificationsLocalDataSource; 9 | 10 | NotificationRepositoryImpl({required this.notificationsLocalDataSource}); 11 | 12 | @override 13 | Future clearNotifications() { 14 | return notificationsLocalDataSource.clearNotifications(); 15 | } 16 | 17 | @override 18 | Future>> getNotifications() { 19 | return notificationsLocalDataSource.getNotifications(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/features/notifications/domain/repository/notifications_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/notifications/data/models/notification.dart'; 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | abstract class NotificationRepository{ 6 | Future>> getNotifications(); 7 | 8 | Future clearNotifications(); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /lib/features/notifications/domain/use_cases/clear_all_notifications_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/notifications/domain/repository/notifications_repository.dart'; 2 | 3 | class ClearAllNotificationsUseCase { 4 | final NotificationRepository notificationRepository; 5 | 6 | ClearAllNotificationsUseCase({required this.notificationRepository}); 7 | 8 | Future execute() async { 9 | return notificationRepository.clearNotifications(); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/features/notifications/domain/use_cases/get_all_notifications_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/features/notifications/data/models/notification.dart'; 3 | import 'package:filmku/features/notifications/domain/repository/notifications_repository.dart'; 4 | 5 | import 'package:filmku/shared/util/app_exception.dart'; 6 | 7 | class GetAllNotificationsUseCase { 8 | final NotificationRepository notificationRepository; 9 | 10 | GetAllNotificationsUseCase({required this.notificationRepository}); 11 | 12 | Future>> execute() async { 13 | return notificationRepository.getNotifications(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/notifications/presentation/provider/notification_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:filmku/features/notifications/presentation/provider/state/notification_state.dart'; 3 | import 'package:filmku/features/notifications/presentation/provider/state/notifications_notifier.dart'; 4 | 5 | final notificationStateProvider = 6 | AutoDisposeStateNotifierProvider( 7 | (ref) => NotificationNotifier()); -------------------------------------------------------------------------------- /lib/features/notifications/presentation/provider/state/notification_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:filmku/features/notifications/data/models/notification.dart'; 3 | 4 | enum NotificationConcreteState { initial, loading, loaded, failure } 5 | 6 | class NotificationState extends Equatable { 7 | final NotificationConcreteState state; 8 | final bool hasData; 9 | final String message; 10 | final bool isLoading; 11 | final List notifications; 12 | 13 | const NotificationState( 14 | {this.state = NotificationConcreteState.initial, 15 | this.hasData = false, 16 | this.message = '', 17 | this.isLoading = false, 18 | this.notifications = const []}); 19 | 20 | const NotificationState.initial( 21 | {this.state = NotificationConcreteState.initial, 22 | this.hasData = false, 23 | this.message = '', 24 | this.isLoading = false, 25 | this.notifications = const []}); 26 | 27 | NotificationState copyWith({ 28 | NotificationConcreteState? state, 29 | bool? hasData, 30 | String? message, 31 | bool? isLoading, 32 | List? notifications, 33 | }) { 34 | return NotificationState( 35 | state: state ?? this.state, 36 | hasData: hasData ?? this.hasData, 37 | message: message ?? this.message, 38 | isLoading: isLoading ?? this.isLoading, 39 | notifications: notifications ?? this.notifications, 40 | ); 41 | } 42 | 43 | @override 44 | List get props =>[notifications,hasData,message,isLoading,state]; 45 | } 46 | -------------------------------------------------------------------------------- /lib/features/notifications/presentation/provider/state/notifications_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/notifications/domain/use_cases/clear_all_notifications_use_case.dart'; 2 | import 'package:filmku/features/notifications/domain/use_cases/get_all_notifications_use_case.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/di/Injector.dart'; 5 | import 'package:filmku/features/notifications/presentation/provider/state/notification_state.dart'; 6 | 7 | class NotificationNotifier extends StateNotifier { 8 | final GetAllNotificationsUseCase _getAllNotificationsUseCase = injector.get(); 9 | final ClearAllNotificationsUseCase _clearAllNotificationsUseCase = injector.get(); 10 | 11 | NotificationNotifier() : super(const NotificationState.initial()); 12 | 13 | bool get isFetching => state.state != NotificationConcreteState.loading; 14 | 15 | Future getAllNotifications() async { 16 | if (isFetching) { 17 | state = state.copyWith(state: NotificationConcreteState.loading); 18 | final response = await _getAllNotificationsUseCase.execute(); 19 | response.fold((failure) { 20 | state = state.copyWith( 21 | state: NotificationConcreteState.failure, 22 | message: failure.message, 23 | hasData: false, 24 | isLoading: false, 25 | notifications: [], 26 | ); 27 | }, (success) { 28 | state = state.copyWith( 29 | state: NotificationConcreteState.loaded, 30 | message: '', 31 | hasData: true, 32 | isLoading: false, 33 | notifications: success, 34 | ); 35 | }); 36 | } 37 | } 38 | 39 | Future clearNotifications() async { 40 | await _clearAllNotificationsUseCase.execute(); 41 | state = state.copyWith( 42 | state: NotificationConcreteState.failure, 43 | message: 'No Notification Available', 44 | hasData: false, 45 | isLoading: false, 46 | notifications: [], 47 | ); 48 | } 49 | 50 | void resetState() { 51 | state = const NotificationState.initial(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/features/notifications/presentation/widget/notification_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:filmku/app/app_colors.dart'; 3 | import 'package:filmku/app/app_dimens.dart'; 4 | import 'package:filmku/features/notifications/data/models/notification.dart'; 5 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 6 | 7 | class NotificationItem extends StatelessWidget { 8 | final NotificationModel notificationModel; 9 | const NotificationItem({Key? key,required this.notificationModel}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | 14 | return Padding( 15 | padding: EdgeInsets.only(top: AppDimens.p4,right: AppDimens.p8,left: AppDimens.p8), 16 | child: Container( 17 | padding: EdgeInsets.only(top: AppDimens.p12,bottom: AppDimens.p12,right: AppDimens.p12,left: AppDimens.p12), 18 | decoration: BoxDecoration( 19 | borderRadius: BorderRadius.circular(AppDimens.p8), 20 | color: context.theme.brightness == Brightness.light 21 | ? notificationModel.positive 22 | ? AppColors.updateNotificationColorLight 23 | : AppColors.deleteNotificationColorLight 24 | : notificationModel.positive 25 | ? AppColors.updateNotificationColorDark // Dark theme - Dark green color for update notifications 26 | : AppColors.deleteNotificationColorDark 27 | ), 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.start, 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | Text('Movie: ${notificationModel.title}', style: context.textTheme.titleSmall!.copyWith(color: AppColors.white)), 33 | Text(notificationModel.message ,style: context.textTheme.bodySmall!.copyWith(color: AppColors.white),) 34 | ], 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:filmku/core/observers.dart'; 6 | 7 | import 'core/app.dart'; 8 | import 'di/Injector.dart'; 9 | 10 | void main() => runMain(); 11 | 12 | Future runMain() async { 13 | await dotenv.load(fileName: ".env"); 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | await initSingletons(); 16 | provideDataSources(); 17 | provideRepositories(); 18 | provideUseCases(); 19 | // Setting Device Orientation 20 | SystemChrome.setPreferredOrientations([ 21 | DeviceOrientation.portraitUp, 22 | DeviceOrientation.portraitDown, 23 | ]); 24 | 25 | //status bar color 26 | // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light.copyWith( 27 | // statusBarColor: AppColors.white, 28 | // statusBarBrightness: Brightness.light, 29 | // )); 30 | 31 | runApp(ProviderScope( 32 | observers: [ 33 | Observers(), 34 | ], 35 | child: const MyApp(), 36 | )); 37 | } 38 | -------------------------------------------------------------------------------- /lib/models/cast.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:isar/isar.dart'; 3 | 4 | part 'cast.freezed.dart'; 5 | 6 | part 'cast.g.dart'; 7 | 8 | @freezed 9 | @Embedded(ignore: {'copyWith'}) 10 | class Cast with _$Cast { 11 | const factory Cast( 12 | {@JsonKey(name: 'id') @Default(0) int id, 13 | @JsonKey(name: 'name') @Default('') String name, 14 | @JsonKey(name: 'original_name') @Default('') String originalName, 15 | @JsonKey(name: 'profile_path') @Default('') String profilePath}) = _Cast; 16 | 17 | factory Cast.fromJson(Map json) => _$CastFromJson(json); 18 | } 19 | -------------------------------------------------------------------------------- /lib/models/casts.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/models/cast.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:isar/isar.dart'; 4 | 5 | part 'casts.freezed.dart'; 6 | 7 | part 'casts.g.dart'; 8 | 9 | @freezed 10 | @Collection(ignore: {'copyWith'}) 11 | class Casts with _$Casts { 12 | const Casts._(); 13 | 14 | const factory Casts({ 15 | @Default(Isar.autoIncrement) int isarId, 16 | @Default(0) int id, 17 | @Default([]) List casts, 18 | }) = _Casts; 19 | 20 | @override 21 | Id get isarId; 22 | } 23 | -------------------------------------------------------------------------------- /lib/models/domain/movies.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/models/movie.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:isar/isar.dart'; 4 | 5 | part 'movies.freezed.dart'; 6 | 7 | part 'movies.g.dart'; 8 | 9 | @freezed 10 | @Collection(ignore: {'copyWith'}) 11 | class Movies with _$Movies { 12 | const Movies._(); 13 | @JsonSerializable(explicitToJson: true) 14 | const factory Movies({ 15 | @Default(Isar.autoIncrement) int id, 16 | @Default([]) List movies, 17 | @Default('') String type, 18 | @Default(0) int page, 19 | @Default(0) int totalPages, 20 | @Default(0) int totalResults, 21 | @Default(false) bool cached, 22 | }) = _Movies; 23 | 24 | @override 25 | Id get id; 26 | } 27 | -------------------------------------------------------------------------------- /lib/models/genre.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:isar/isar.dart'; 3 | 4 | part 'genre.freezed.dart'; 5 | 6 | part 'genre.g.dart'; 7 | 8 | @freezed 9 | @Embedded(ignore: {'copyWith'}) 10 | class Genre with _$Genre { 11 | const factory Genre( 12 | {@JsonKey(name: 'id') @Default(0) int id, 13 | @JsonKey(name: 'name') @Default('') String name}) = _Genre; 14 | 15 | factory Genre.fromJson(Map json) => _$GenreFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/models/genres.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/models/genre.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:isar/isar.dart'; 4 | 5 | part 'genres.freezed.dart'; 6 | part 'genres.g.dart'; 7 | @freezed 8 | @Collection(ignore: {'copyWith'}) 9 | class Genres with _$Genres{ 10 | 11 | const Genres._(); 12 | const factory Genres({ 13 | @Default(Isar.autoIncrement) int id, 14 | @Default([]) List genres, 15 | })= _Genres; 16 | 17 | @override 18 | Id get id; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /lib/models/message.dart: -------------------------------------------------------------------------------- 1 | class Message { 2 | final String content; 3 | final MessageType type; 4 | final DateTime timestamp; 5 | 6 | Message(this.content, this.type, this.timestamp); 7 | } 8 | 9 | enum MessageType { 10 | error, 11 | success, 12 | info, 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/movie.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:isar/isar.dart'; 3 | 4 | part 'movie.freezed.dart'; 5 | 6 | part 'movie.g.dart'; 7 | 8 | 9 | @freezed 10 | @Embedded(ignore: {'copyWith'}) 11 | class Movie with _$Movie { 12 | @JsonSerializable(explicitToJson: true) 13 | const factory Movie( 14 | {@JsonKey(name: 'adult') @Default(false) bool adult, 15 | @JsonKey(name: 'backdrop_path') @Default('') String backdropPath, 16 | @JsonKey(name: 'genre_ids') @Default([]) List genreIds, 17 | @JsonKey(name: 'id') @Default(0) int id, 18 | @JsonKey(name: 'original_language') @Default('') String originalLanguage, 19 | @JsonKey(name: 'original_title') @Default('') String originalTitle, 20 | @JsonKey(name: 'overview') @Default('') String overview, 21 | @JsonKey(name: 'popularity') @Default(0.0) double popularity, 22 | @JsonKey(name: 'poster_path') @Default('') String posterPath, 23 | @JsonKey(name: 'release_date') @Default('') String releaseDate, 24 | @JsonKey(name: 'title') @Default('') String title, 25 | @JsonKey(name: 'video') @Default(false) bool video, 26 | @JsonKey(name: 'vote_average') @Default(0.0) double voteAverage, 27 | @JsonKey(name: 'vote_count') @Default(0) int voteCount}) = _Movie; 28 | 29 | factory Movie.fromJson(Map json) => _$MovieFromJson(json); 30 | } 31 | -------------------------------------------------------------------------------- /lib/models/movie_detail.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/models/genre.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:isar/isar.dart'; 4 | 5 | part 'movie_detail.freezed.dart'; 6 | 7 | part 'movie_detail.g.dart'; 8 | 9 | @freezed 10 | @Collection(ignore: {'copyWith'}) 11 | class MovieDetail with _$MovieDetail { 12 | const MovieDetail._(); 13 | 14 | @JsonSerializable(explicitToJson: true) 15 | const factory MovieDetail( 16 | {@Default(Isar.autoIncrement) int isarId, 17 | @JsonKey(name: 'id') @Default(0) int id, 18 | @JsonKey(name: 'vote_count') @Default(0) int voteCount, 19 | @JsonKey(name: 'runtime') @Default(0) int runtime, 20 | @JsonKey(name: 'revenue') @Default(0) int revenue, 21 | @JsonKey(name: 'vote_average') @Default(0.0) double voteAverage, 22 | @JsonKey(name: 'popularity') @Default(0.0) double popularity, 23 | @JsonKey(name: 'adult') @Default(false) bool adult, 24 | @JsonKey(name: 'video') @Default(false) bool video, 25 | @JsonKey(name: 'backdrop_path') @Default('') String backdropPath, 26 | @JsonKey(name: 'status') @Default('') String status, 27 | @JsonKey(name: 'tagline') @Default('') String tagline, 28 | @JsonKey(name: 'title') @Default('') String title, 29 | @JsonKey(name: 'release_date') @Default('') String releaseDate, 30 | @JsonKey(name: 'poster_path') @Default('') String posterPath, 31 | @JsonKey(name: 'imdb_id') @Default('') String imdbId, 32 | @JsonKey(name: 'original_language') @Default('') String originalLanguage, 33 | @JsonKey(name: 'homepage') @Default('') String homepage, 34 | @JsonKey(name: 'original_title') @Default('') String originalTitle, 35 | @JsonKey(name: 'overview') @Default('') String overview, 36 | @JsonKey(name: 'genres') @Default([]) List genres}) = _MovieDetail; 37 | 38 | factory MovieDetail.fromJson(Map json) => 39 | _$MovieDetailFromJson(json); 40 | 41 | @override 42 | Id get isarId ; 43 | } 44 | -------------------------------------------------------------------------------- /lib/models/response/casts_response.dart: -------------------------------------------------------------------------------- 1 | class CastsResponse { 2 | final List casts; 3 | 4 | CastsResponse(this.casts); 5 | 6 | @override 7 | String toString() { 8 | return 'CastsResponse{casts: $casts}'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/models/response/genre_response.dart: -------------------------------------------------------------------------------- 1 | class GenreResponse { 2 | final List genres; 3 | 4 | GenreResponse(this.genres); 5 | 6 | @override 7 | String toString() { 8 | return 'GenreResponse{genres: $genres}'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/models/response/movie_detail_response.dart: -------------------------------------------------------------------------------- 1 | class MovieDetailResponse{ 2 | 3 | } 4 | 5 | -------------------------------------------------------------------------------- /lib/models/response/movies_response.dart: -------------------------------------------------------------------------------- 1 | class MoviesResponse { 2 | final int page; 3 | final int totalPages; 4 | final int totalResults; 5 | final List results; 6 | 7 | MoviesResponse({ 8 | required this.page, 9 | required this.totalPages, 10 | required this.totalResults, 11 | required this.results, 12 | }); 13 | 14 | factory MoviesResponse.fromJson(dynamic json, List results, 15 | {Function(dynamic json)? fixtures}) => 16 | MoviesResponse( 17 | page: json['page'], 18 | totalPages: json['total_pages'], 19 | totalResults: json['total_results'], 20 | results: results); 21 | 22 | @override 23 | String toString() { 24 | return 'MoviesResponse{page: $page, totalPages: $totalPages, totalResults: $totalResults, results: $results}'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/models/response/response.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | 6 | class Response { 7 | final int statusCode; 8 | final String? statusMessage; 9 | final dynamic data; 10 | 11 | Response( 12 | {required this.statusCode, 13 | required this.statusMessage, 14 | required this.data}); 15 | 16 | @override 17 | String toString() { 18 | return 'Response{statusCode: $statusCode, statusMessage: $statusMessage, data: $data}'; 19 | } 20 | } 21 | 22 | extension ResponseExtension on Response { 23 | Right get toRight => Right(this); 24 | } 25 | -------------------------------------------------------------------------------- /lib/routes/app_router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/features/home/presentation/screens/home_page.dart'; 3 | import 'package:filmku/features/movie_detail/presentation/screen/movie_detail_screen.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 6 | 7 | part 'app_router.g.dart'; 8 | 9 | enum Routes { 10 | home, 11 | movieDetail, 12 | bookmarks, 13 | } 14 | 15 | final _rootNavigatorKey = GlobalKey(); 16 | 17 | @Riverpod(keepAlive: true) 18 | GoRouter goRouter(GoRouterRef ref) { 19 | return GoRouter( 20 | initialLocation: '/', 21 | navigatorKey: _rootNavigatorKey, 22 | debugLogDiagnostics: false, 23 | routes: [ 24 | GoRoute( 25 | path: '/', 26 | name: Routes.home.name, 27 | builder: (context, state) => HomePage( 28 | key: state.pageKey, 29 | ), 30 | ), 31 | GoRoute( 32 | path: '/movieDetail', 33 | name: Routes.movieDetail.name, 34 | builder: (context, state) => MovieDetailScreen( 35 | key: state.pageKey, movieId: state.extra as int)), 36 | ], 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/routes/app_router.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'app_router.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$goRouterHash() => r'208e243e4a747c5b1fff086b558e78831b5601e5'; 10 | 11 | /// See also [goRouter]. 12 | @ProviderFor(goRouter) 13 | final goRouterProvider = Provider.internal( 14 | goRouter, 15 | name: r'goRouterProvider', 16 | debugGetCreateSourceHash: 17 | const bool.fromEnvironment('dart.vm.product') ? null : _$goRouterHash, 18 | dependencies: null, 19 | allTransitiveDependencies: null, 20 | ); 21 | 22 | typedef GoRouterRef = ProviderRef; 23 | // ignore_for_file: type=lint 24 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member 25 | -------------------------------------------------------------------------------- /lib/shared/extensions/build_context_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension BuildContextExtension on BuildContext { 4 | ThemeData get theme => Theme.of(this); 5 | 6 | TextTheme get textTheme => Theme.of(this).textTheme; 7 | 8 | double get sizeWidth => MediaQuery.of(this).size.width; 9 | 10 | double get sizeHeight => MediaQuery.of(this).size.height; 11 | 12 | Orientation get orientation => MediaQuery.of(this).orientation; 13 | } 14 | -------------------------------------------------------------------------------- /lib/shared/local/cache/local_db.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | abstract class LocalDb { 4 | Future initDb(); 5 | Isar getDb(); 6 | Future cleanDb(); 7 | 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/shared/local/cache/local_db_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/features/notifications/data/models/notification.dart'; 2 | import 'package:filmku/models/domain/movies.dart'; 3 | import 'package:filmku/models/genres.dart'; 4 | import 'package:filmku/models/movie_detail.dart'; 5 | import 'package:isar/isar.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | 8 | import 'local_db.dart'; 9 | 10 | 11 | class InitDbImpl extends LocalDb { 12 | late Isar db; 13 | 14 | @override 15 | Future initDb() async { 16 | final dir = await getApplicationDocumentsDirectory(); 17 | db = await Isar.open( 18 | [MoviesSchema, GenresSchema, MovieDetailSchema, NotificationModelSchema], 19 | directory: dir.path, 20 | ); 21 | } 22 | 23 | @override 24 | Isar getDb() { 25 | return db; 26 | } 27 | 28 | @override 29 | Future cleanDb() async { 30 | await db.writeTxn(() => cleanDb()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/shared/local/shared_prefs/shared_pref.dart: -------------------------------------------------------------------------------- 1 | abstract class SharedPref { 2 | void init(); 3 | 4 | bool get hasInitialized; 5 | 6 | Future remove(String key); 7 | 8 | Future get(String key); 9 | 10 | Future set(String key, String data); 11 | 12 | Future clear(); 13 | 14 | Future has(String key); 15 | } 16 | -------------------------------------------------------------------------------- /lib/shared/local/shared_prefs/shared_pref_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:filmku/shared/local/shared_prefs/shared_pref.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class SharedPrefImplementation implements SharedPref { 7 | SharedPreferences? sharedPreferences; 8 | final Completer initCompleter = Completer(); 9 | SharedPrefImplementation(){ 10 | init(); 11 | } 12 | 13 | @override 14 | void init() { 15 | initCompleter.complete(SharedPreferences.getInstance()); 16 | } 17 | 18 | @override 19 | bool get hasInitialized => sharedPreferences != null; 20 | 21 | @override 22 | Future set(String key, String data) async { 23 | sharedPreferences = await initCompleter.future; 24 | return await sharedPreferences!.setString(key, data.toString()); 25 | } 26 | 27 | @override 28 | Future get(String key) async { 29 | sharedPreferences = await initCompleter.future; 30 | return sharedPreferences!.get(key); 31 | } 32 | 33 | @override 34 | Future has(String key) async { 35 | sharedPreferences = await initCompleter.future; 36 | return sharedPreferences?.containsKey(key) ?? false; 37 | } 38 | 39 | @override 40 | Future remove(String key) async { 41 | sharedPreferences = await initCompleter.future; 42 | return await sharedPreferences!.remove(key); 43 | } 44 | 45 | @override 46 | Future clear() async { 47 | sharedPreferences = await initCompleter.future; 48 | await sharedPreferences!.clear(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/shared/network/dio_network_service.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 6 | import 'package:filmku/app/app_globals.dart'; 7 | import 'package:filmku/models/response/response.dart' as response; 8 | import 'package:filmku/shared/network/network_service.dart'; 9 | import '../util/app_exception.dart'; 10 | import 'exception/mixin/network_handler_mixin.dart'; 11 | import 'network_values.dart'; 12 | 13 | class DioNetworkService extends NetworkService with ExceptionHandlerMixin { 14 | late Dio dio; 15 | 16 | DioNetworkService() { 17 | dio = Dio(); 18 | if (!kTestMode) { 19 | dio.options = dioBaseOptions; 20 | if (kDebugMode) { 21 | dio.interceptors 22 | .add(LogInterceptor(requestBody: true, responseBody: true)); 23 | } 24 | } 25 | } 26 | 27 | BaseOptions get dioBaseOptions => BaseOptions( 28 | baseUrl: baseUrl, 29 | headers: headers, 30 | connectTimeout: const Duration(seconds: 20), 31 | receiveTimeout: const Duration(seconds: 20)); 32 | 33 | @override 34 | String get baseUrl => dotenv.env[NetworkEnv.BASE_URL.name] ?? ''; 35 | 36 | @override 37 | String get apiKey => dotenv.env[NetworkEnv.API_KEY.name] ?? ''; 38 | 39 | @override 40 | Map get headers => { 41 | 'accept': 'application/json', 42 | 'content-type': 'application/json', 43 | }; 44 | 45 | @override 46 | Map? updateHeaders(Map data) { 47 | final header = {...data, ...headers}; 48 | if (!kTestMode) { 49 | dio.options.headers = headers; 50 | } 51 | return header; 52 | } 53 | 54 | @override 55 | Future> get(String endPoint, {Map? queryParams}) { 56 | queryParams ??= {}; 57 | queryParams[Params.apiKey]=apiKey; 58 | final res = handleException( 59 | () => dio.get( 60 | endPoint, 61 | queryParameters: queryParams, 62 | ), 63 | endPoint: endPoint, 64 | ); 65 | return res; 66 | } 67 | 68 | @override 69 | Future> post(String endPoint, 70 | {Map? data}) { 71 | // queryParams[Params.apiKey] = apiKey; 72 | final res = handleException( 73 | () => dio.post( 74 | endPoint, 75 | data: data, 76 | ), 77 | endPoint: endPoint); 78 | return res; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/shared/network/exception/mixin/network_handler_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:filmku/models/response/response.dart' as response; 6 | import 'package:filmku/shared/network/network_service.dart'; 7 | 8 | import 'package:filmku/shared/util/app_exception.dart'; 9 | 10 | mixin ExceptionHandlerMixin on NetworkService { 11 | Future> handleException(Future> Function() handler, {String endPoint = ''}) async { 12 | try { 13 | final res = await handler(); 14 | return Right(response.Response( 15 | statusCode: res.statusCode ?? 200, 16 | data: res.data, 17 | statusMessage: res.statusMessage, 18 | )); 19 | } catch (ex) { 20 | String message = ''; 21 | String identifier = ''; 22 | int statusCode = 0; 23 | switch (ex.runtimeType) { 24 | case SocketException: 25 | ex as SocketException; 26 | message = 'unable to connect to the server.'; 27 | statusCode = 0; 28 | identifier = 'Socket Exception ${ex.message}\n at $endPoint'; 29 | break; 30 | 31 | case DioException: 32 | ex as DioException; 33 | message = ex.response?.data?['message'] ?? 'Internal Error occurred'; 34 | statusCode = 0; 35 | identifier = 'Dio Exception ${ex.message}\n at $endPoint'; 36 | break; 37 | 38 | default: 39 | message = 'unknown error occurred'; 40 | statusCode = 0; 41 | identifier = 'unknown error ${ex.toString()}\n at $endPoint'; 42 | break; 43 | } 44 | return Left(AppException( 45 | message: message, statusCode: statusCode, identifier: identifier,which: 'http')); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/shared/network/network_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/response/response.dart'; 3 | import 'package:filmku/shared/util/app_exception.dart'; 4 | 5 | abstract class NetworkService { 6 | String get baseUrl; 7 | 8 | String get apiKey; 9 | 10 | Map get headers; 11 | 12 | void updateHeaders(Map data); 13 | 14 | Future> get( 15 | String endPoint, { 16 | Map? queryParams, 17 | }); 18 | 19 | Future> post(String endPoint, 20 | {Map? data}); 21 | } 22 | 23 | enum NetworkEnv { BASE_URL, API_KEY } 24 | -------------------------------------------------------------------------------- /lib/shared/network/network_values.dart: -------------------------------------------------------------------------------- 1 | class EndPoints { 2 | static const String nowShowing = 'movie/now_playing'; 3 | static const String popular = 'movie/top_rated'; 4 | static const String genre = 'genre/movie/list'; 5 | 6 | static String movie(id) => 'movie/$id'; 7 | 8 | static String casts(int id) => '/movie/$id/credits'; 9 | } 10 | 11 | class Params { 12 | static const String page = 'page'; 13 | static const String apiKey = 'api_key'; 14 | } 15 | -------------------------------------------------------------------------------- /lib/shared/network/remote.dart: -------------------------------------------------------------------------------- 1 | export 'dio_network_service.dart'; 2 | export 'network_service.dart'; 3 | -------------------------------------------------------------------------------- /lib/shared/provider/app_theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/shared/provider/state/theme_state.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:filmku/app/app_constants.dart'; 5 | import 'package:filmku/di/Injector.dart'; 6 | import 'package:filmku/shared/local/shared_prefs/shared_pref.dart'; 7 | 8 | final appThemeProvider = 9 | StateNotifierProvider((ref) { 10 | final sharedPref = injector.get(); 11 | return AppThemeChangeNotifier(sharedPref); 12 | }); 13 | 14 | class AppThemeChangeNotifier extends StateNotifier { 15 | final SharedPref sharedPref; 16 | 17 | ThemeMode currentTheme = ThemeMode.light; 18 | 19 | AppThemeChangeNotifier(this.sharedPref) : super(const ThemeState()) { 20 | getCurrentTheme(); 21 | } 22 | 23 | void setDarkTheme() { 24 | state = state.copyWith(currentTheme: ThemeMode.dark, selectedTheme: 'dark'); 25 | sharedPref.set(AppConstants.CURRENT_THEME, state.selectedTheme); 26 | } 27 | 28 | void setLightTheme() { 29 | state = 30 | state.copyWith(currentTheme: ThemeMode.light, selectedTheme: 'light'); 31 | sharedPref.set(AppConstants.CURRENT_THEME, state.selectedTheme); 32 | } 33 | 34 | void setDefaultTheme() { 35 | sharedPref.set(AppConstants.CURRENT_THEME, 'default'); 36 | final defaultThemeMode = WidgetsBinding.instance.window.platformBrightness; 37 | final value = ThemeMode.values.byName(defaultThemeMode.name); 38 | state = state.copyWith(currentTheme: value, selectedTheme: 'default'); 39 | } 40 | 41 | void getCurrentTheme() async { 42 | final String? theme = 43 | await sharedPref.get(AppConstants.CURRENT_THEME) as String?; 44 | if (theme == null || theme == 'default') { 45 | final defaultThemeMode = 46 | WidgetsBinding.instance.window.platformBrightness; 47 | final value = ThemeMode.values.byName(defaultThemeMode.name); 48 | state = state.copyWith(currentTheme: value, selectedTheme: 'default'); 49 | } else { 50 | final value = ThemeMode.values.byName(theme); 51 | state = state.copyWith(currentTheme: value, selectedTheme: value.name); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /lib/shared/provider/message_queue_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | 3 | final messageQueueProvider = StateNotifierProvider>((ref) { 4 | return MessageQueue(); 5 | }); 6 | 7 | class MessageQueue extends StateNotifier> { 8 | MessageQueue() : super([]); 9 | 10 | void addMessage(String message) { 11 | state = [...state, message]; 12 | } 13 | 14 | void clearMessages() { 15 | state = []; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/shared/provider/state/theme_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ThemeState extends Equatable { 5 | final ThemeMode currentTheme; 6 | final String selectedTheme; 7 | 8 | const ThemeState({ 9 | this.currentTheme = ThemeMode.light, 10 | this.selectedTheme = 'light' 11 | }); 12 | 13 | ThemeState copyWith({ThemeMode? currentTheme, String? selectedTheme}) { 14 | return ThemeState( 15 | currentTheme: currentTheme ?? this.currentTheme, 16 | selectedTheme: selectedTheme ?? this.selectedTheme 17 | ); 18 | } 19 | 20 | 21 | @override 22 | List get props => [currentTheme,selectedTheme]; 23 | } 24 | -------------------------------------------------------------------------------- /lib/shared/util/app_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:filmku/models/response/response.dart'; 3 | 4 | class AppException implements Exception { 5 | final String? message; 6 | final int? statusCode; 7 | final String? identifier; 8 | final String? which; 9 | 10 | AppException( 11 | {required this.message, 12 | required this.statusCode, 13 | required this.identifier, 14 | required this.which}); 15 | 16 | @override 17 | String toString() { 18 | return 'AppException{message: $message, statusCode: $statusCode, identifier: $identifier, which: $which}'; 19 | } 20 | } 21 | 22 | extension HttpExceptionExtension on AppException { 23 | Left get toLeft => Left(this); 24 | } 25 | -------------------------------------------------------------------------------- /lib/shared/util/message_queue.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/lib/shared/util/message_queue.dart -------------------------------------------------------------------------------- /lib/shared/widgets/app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:filmku/app/app_colors.dart'; 4 | import 'package:filmku/app/app_strings.dart'; 5 | import 'package:filmku/features/notifications/presentation/screen/notification_screen.dart'; 6 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 7 | 8 | class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { 9 | const CustomAppBar({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final Color iconColor = context.theme.brightness == Brightness.light 14 | ? AppColors.primaryLight 15 | : AppColors.primaryDark; 16 | return AppBar( 17 | title: Text( 18 | AppStrings.appName, 19 | style: context.textTheme.titleMedium, 20 | ), 21 | centerTitle: true, 22 | leading: GestureDetector( 23 | onTap: () { 24 | Scaffold.of(context).openDrawer(); 25 | }, 26 | child: Padding( 27 | padding: const EdgeInsets.all(8.0), 28 | child: Icon( 29 | Icons.menu, 30 | color: iconColor, 31 | ), 32 | ), 33 | ), 34 | actions: [ 35 | Padding( 36 | padding: const EdgeInsets.all(8.0), 37 | child: IconButton( 38 | onPressed: () { 39 | notificationBottomSheet(context); 40 | }, 41 | icon: Icon( 42 | Icons.notification_add, 43 | color: iconColor, 44 | )), 45 | ) 46 | ], 47 | ); 48 | } 49 | 50 | @override 51 | Size get preferredSize => Size.fromHeight(50.sp); 52 | } 53 | -------------------------------------------------------------------------------- /lib/shared/widgets/app_bottom_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppBottomNavigation extends StatelessWidget { 4 | final int currentIndex ; 5 | final Function(int) onTapped; 6 | 7 | const AppBottomNavigation({Key? key, required this.currentIndex,required this.onTapped}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return BottomNavigationBar( 12 | onTap: (index) => onTapped(index), 13 | currentIndex: currentIndex, 14 | items: const [ 15 | BottomNavigationBarItem(icon: Icon(Icons.movie), label: ''), 16 | BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: '') 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/shared/widgets/drawer_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:filmku/app/app_colors.dart'; 2 | import 'package:filmku/app/app_dimens.dart'; 3 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_svg/flutter_svg.dart'; 6 | 7 | class DrawerItem extends StatelessWidget { 8 | final String title; 9 | final String asset; 10 | final bool isSelected; 11 | final VoidCallback? onTap; 12 | 13 | const DrawerItem( 14 | {Key? key, 15 | required this.title, 16 | required this.asset, 17 | required this.onTap, 18 | required this.isSelected}) 19 | : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Padding( 24 | padding: EdgeInsets.only(left: AppDimens.p10), 25 | child: InkWell( 26 | onTap: onTap, 27 | child: ListTile( 28 | dense: true, 29 | leading: SvgPicture.asset( 30 | asset, 31 | height: 20, 32 | width: 20, 33 | color: context.theme.primaryColor, 34 | ), 35 | title: Text( 36 | title, 37 | style: 38 | context.textTheme.bodyMedium!.copyWith(color: context.theme.primaryColor), 39 | ), 40 | trailing: isSelected 41 | ? const Icon( 42 | Icons.check_circle, 43 | color: AppColors.ratingIconColor, 44 | ) 45 | : null), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/shared/widgets/genre_chip.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/app/app_colors.dart'; 3 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 4 | 5 | class GenreChip extends StatelessWidget { 6 | final String label; 7 | 8 | const GenreChip({Key? key, required this.label}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: const EdgeInsets.only(left: 8), 14 | child: Chip( 15 | label: Text( 16 | label, 17 | style: context.textTheme.bodySmall!.copyWith(color: context.theme.brightness == Brightness.light? AppColors.chipTextLight:AppColors.chipTextDark), 18 | ), 19 | backgroundColor: context.theme.brightness == Brightness.light? AppColors.chipColorLight:AppColors.chipColorDark , 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/shared/widgets/message_queue_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:filmku/shared/widgets/message_widget.dart'; 3 | 4 | class MessageQueueWrapper extends StatelessWidget { 5 | final Widget child; 6 | 7 | MessageQueueWrapper({super.key, required this.child}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | body: Stack( 13 | children: [ 14 | child, // Your current screen's content 15 | const Positioned( 16 | bottom: 0, 17 | left: 0, 18 | right: 0, 19 | child: MessageQueueWidget(), 20 | ), 21 | ], 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/shared/widgets/message_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | 4 | import '../provider/message_queue_provider.dart'; 5 | 6 | class MessageQueueWidget extends ConsumerWidget { 7 | const MessageQueueWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | final messages = ref.watch(messageQueueProvider); 12 | 13 | return Builder( 14 | builder: (BuildContext context) { 15 | if (messages.isEmpty) { 16 | return Container(); 17 | } 18 | 19 | WidgetsBinding.instance.addPostFrameCallback((_) { 20 | // Show SnackBar after the frame has been rendered. 21 | ScaffoldMessenger.of(context).showSnackBar( 22 | SnackBar( 23 | content: Text(messages.last), 24 | ), 25 | ); 26 | ref.read(messageQueueProvider.notifier).clearMessages(); 27 | }); 28 | 29 | return Container(); 30 | }, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/shared/widgets/rating_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:filmku/app/app_colors.dart'; 4 | 5 | class RatingBar extends StatelessWidget { 6 | final double rating; 7 | const RatingBar({Key? key, required this.rating}) : super(key: key); 8 | @override 9 | Widget build(BuildContext context) { 10 | return Row( 11 | children: [ 12 | const Icon(Icons.star, color: AppColors.ratingIconColor), 13 | Text( 14 | '$rating/10 IMDb', 15 | style: Theme.of(context).textTheme.bodySmall, 16 | ) 17 | ], 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/shared/widgets/see_more.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:filmku/app/app_strings.dart'; 4 | import 'package:filmku/shared/extensions/build_context_extensions.dart'; 5 | 6 | class SeeMore extends StatelessWidget { 7 | const SeeMore({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return OutlinedButton( 12 | style: OutlinedButton.styleFrom( 13 | shape: RoundedRectangleBorder( 14 | borderRadius: BorderRadius.circular(18.sp), 15 | ), 16 | ), 17 | onPressed: () {}, 18 | child: Text( 19 | AppStrings.seeMore, 20 | style: context.textTheme.bodySmall, 21 | )); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/shared/widgets/shimmers/skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | class Skeleton extends StatelessWidget { 5 | final double width, height, left, right, top, bottom; 6 | 7 | const Skeleton( 8 | {Key? key, 9 | required this.height, 10 | required this.width, 11 | required this.left, 12 | required this.right, 13 | required this.top, 14 | required this.bottom}) 15 | : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Padding( 20 | padding: EdgeInsets.only( 21 | top: top.sp, bottom: bottom.sp, left: left.sp, right: right.sp), 22 | child: ClipRRect( 23 | borderRadius: BorderRadius.circular(8.sp), 24 | child: Container( 25 | height: height.sp, 26 | width: width.sp, 27 | color: Colors.grey, 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); 14 | isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | isar_flutter_libs 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /screenshots/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/ae99a2ad5ced749937f8a5e1bc2a233b49b2ed53/screenshots/screenshots.png --------------------------------------------------------------------------------