├── .env
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── flutter_clean_architecture
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── 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
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
└── icons
│ ├── facebook.svg
│ ├── imdb.svg
│ ├── instagram.svg
│ ├── tiktok.svg
│ ├── twitter.svg
│ └── youtube.svg
├── coverage
└── lcov.info
├── 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
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── RunnerTests
│ └── RunnerTests.swift
├── lib
├── main.dart
└── src
│ ├── config
│ ├── gen
│ │ └── assets.gen.dart
│ └── router
│ │ ├── app_router.dart
│ │ └── app_router.gr.dart
│ ├── core
│ ├── components
│ │ ├── bottom_sheet
│ │ │ ├── _mixin
│ │ │ │ └── base_bottom_sheet_mixin.dart
│ │ │ └── social_media_bottom_sheet.dart
│ │ ├── buttons
│ │ │ ├── bookmark_button.dart
│ │ │ ├── retry_button.dart
│ │ │ └── social_button.dart
│ │ ├── card
│ │ │ └── movie_card.dart
│ │ ├── image
│ │ │ └── base_network_image.dart
│ │ └── indicator
│ │ │ └── base_indicator.dart
│ ├── constants
│ │ ├── app_constants.dart
│ │ ├── image_constants.dart
│ │ ├── path_constants.dart
│ │ └── url_constants.dart
│ ├── database
│ │ └── local_database.dart
│ ├── exceptions
│ │ ├── database
│ │ │ └── database_exception.dart
│ │ └── network
│ │ │ └── network_exception.dart
│ ├── extensions
│ │ └── int_extensions.dart
│ ├── network
│ │ ├── dio_client.dart
│ │ └── model
│ │ │ ├── error_model.dart
│ │ │ └── error_model.g.dart
│ ├── theme
│ │ ├── app_theme.dart
│ │ └── cubit
│ │ │ ├── theme_cubit.dart
│ │ │ └── theme_state.dart
│ └── utils
│ │ └── url_launcher_manager.dart
│ ├── data
│ ├── datasources
│ │ ├── _mappers
│ │ │ └── entity_convertable.dart
│ │ ├── export_datasources.dart
│ │ ├── local
│ │ │ ├── _collections
│ │ │ │ ├── export_collections.dart
│ │ │ │ └── movie_detail
│ │ │ │ │ ├── movie_detail_collection.dart
│ │ │ │ │ └── movie_detail_collection.g.dart
│ │ │ └── movie
│ │ │ │ ├── movie_local_data_source.dart
│ │ │ │ └── movie_local_data_source_impl.dart
│ │ └── remote
│ │ │ ├── actor
│ │ │ ├── actor_remote_data_source.dart
│ │ │ └── actor_remote_data_source_impl.dart
│ │ │ └── movie
│ │ │ ├── movie_remote_data_source.dart
│ │ │ └── movie_remote_data_source_impl.dart
│ ├── models
│ │ ├── actor_detail
│ │ │ ├── actor_detail_model.dart
│ │ │ └── actor_detail_model.g.dart
│ │ ├── actor_social_media
│ │ │ ├── actor_social_media_model.dart
│ │ │ └── actor_social_media_model.g.dart
│ │ ├── export_models.dart
│ │ ├── movie_credit
│ │ │ ├── cast_model.dart
│ │ │ ├── cast_model.g.dart
│ │ │ ├── crew_model.dart
│ │ │ ├── crew_model.g.dart
│ │ │ ├── movie_credit_model.dart
│ │ │ └── movie_credit_model.g.dart
│ │ ├── movie_detail
│ │ │ ├── movie_detail_model.dart
│ │ │ └── movie_detail_model.g.dart
│ │ └── movie_listings
│ │ │ ├── movie_listings_model.dart
│ │ │ └── movie_listings_model.g.dart
│ └── repositories
│ │ ├── actor
│ │ └── actor_repository_impl.dart
│ │ ├── export_repository_impls.dart
│ │ └── movie
│ │ └── movie_repository_impl.dart
│ ├── domain
│ ├── entities
│ │ ├── actor_detail
│ │ │ └── actor_detail_entity.dart
│ │ ├── actor_social_media
│ │ │ └── actor_social_medias_entity.dart
│ │ ├── export_entities.dart
│ │ ├── movie_credit
│ │ │ ├── cast_entity.dart
│ │ │ ├── crew_entity.dart
│ │ │ └── movie_credit_entity.dart
│ │ ├── movie_detail
│ │ │ └── movie_detail_entity.dart
│ │ └── movie_listings
│ │ │ └── movie_listings_entity.dart
│ ├── repositories
│ │ ├── actor
│ │ │ └── actor_repository.dart
│ │ ├── export_repositories.dart
│ │ └── movie
│ │ │ └── movie_repository.dart
│ └── usecases
│ │ ├── actor
│ │ └── actor_usecases.dart
│ │ ├── export_usecases.dart
│ │ └── movie
│ │ └── movie_usecases.dart
│ ├── injector.dart
│ └── presentation
│ ├── _widget
│ ├── movie_detail
│ │ ├── actor_card.dart
│ │ └── tag_container.dart
│ └── movies
│ │ └── movie_listing_widget.dart
│ ├── cubit
│ ├── actor
│ │ ├── export_actor_cubits.dart
│ │ ├── get_actor_detail
│ │ │ ├── get_actor_detail_cubit.dart
│ │ │ └── get_actor_detail_state.dart
│ │ └── get_actor_social_media
│ │ │ ├── get_actor_social_media_cubit.dart
│ │ │ └── get_actor_social_media_state.dart
│ └── movie
│ │ ├── export_movie_cubits.dart
│ │ ├── get_movie_credits
│ │ ├── get_movie_credits_cubit.dart
│ │ └── get_movie_credits_state.dart
│ │ ├── get_popular_movies
│ │ ├── get_popular_movies_cubit.dart
│ │ └── get_popular_movies_state.dart
│ │ ├── get_saved_movies
│ │ ├── get_saved_movies_cubit.dart
│ │ └── get_saved_movies_state.dart
│ │ ├── get_top_rated_movies
│ │ ├── get_top_rated_movies_cubit.dart
│ │ └── get_top_rated_movies_state.dart
│ │ └── toggle_bookmark
│ │ ├── toggle_bookmark_cubit.dart
│ │ └── toggle_bookmark_state.dart
│ └── view
│ ├── bookmarks_view.dart
│ ├── master_view.dart
│ ├── movie_detail_view.dart
│ └── movies_view.dart
├── pubspec.lock
├── pubspec.yaml
├── scripts
└── refresh_script.sh
└── test
├── _utils
├── _dummy
│ ├── actor_detail_dummy_data.json
│ ├── actor_social_media_data.json
│ ├── movie_credit_dummy_data.json
│ └── movie_listings_dummy_data.json
├── json_reader.dart
└── mocks
│ ├── mocks.dart
│ └── mocks.mocks.dart
├── data
└── remote
│ ├── actor
│ └── actor_remote_data_source_test.dart
│ └── movie
│ └── movie_remote_data_source_test.dart
├── domain
├── repositories
│ ├── actor
│ │ └── actor_repository_test.dart
│ └── movie
│ │ └── movie_repository_test.dart
└── usecases
│ ├── actor
│ └── actor_usecases_test.dart
│ └── movie
│ └── movie_usecases_test.dart
└── presentation
└── cubit
├── actor
├── get_actor_detail_cubit_test.dart
└── get_actor_social_media_test.dart
└── movie
├── get_movie_credits_cubit_test.dart
├── get_popular_movies_cubit_test.dart
├── get_saved_movies_cubit_test.dart
├── get_top_rated_movies_cubit_test.dart
└── toggle_bookmark_cubit_test.dart
/.env:
--------------------------------------------------------------------------------
1 | BASE_URL = "https://api.themoviedb.org/3"
2 |
3 | #* your TMDB token. (sign up here https://developer.themoviedb.org/docs)
4 | API_TOKEN = "your tmdb token is here"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .packages
33 | .pub-cache/
34 | .pub/
35 | /build/
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
--------------------------------------------------------------------------------
/.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: "ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a"
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: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
17 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
18 | - platform: android
19 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
20 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
21 | - platform: ios
22 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
23 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
24 | - platform: linux
25 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
26 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
27 | - platform: macos
28 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
29 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
30 | - platform: web
31 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
32 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
33 | - platform: windows
34 | create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
35 | base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Enes Akbal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Clean Architecture Example
2 |
3 | ### About This Project
4 |
5 | This repository is the companion code for my 5-part Medium article series titled "Flutter Clean Architecture." The series offers an extensive guide on applying clean architecture principles in Flutter projects. Each part focuses on a different layer of architecture, backed by practical examples from this repository.
6 |
7 | Feel free to fork this repository and submit a pull request if you have suggestions or improvements.
8 |
9 | ### Article Series Overview
10 |
11 |
12 | #### Part 1: Introduction
13 | - Introduction to the project and the fundamental concepts
14 | - Why should we Use Clean Architecture in Flutter?
15 | - Advantages of The Clean Architecture
16 | - [Read Part 1 on Medium](https://medium.com/@enesakbal00/flutter-clean-architecture-part-1-introduction-f5dadf1bf3ee)
17 | #### Part 2: The Domain Layer
18 | - Insights into the core logic and business rules
19 | - Usecases
20 | - [Read Part 2 on Medium](https://medium.com/@enesakbal00/flutter-clean-architecture-part-2-domain-layer-f55007bd1ade)
21 | #### Part 3: The Data Layer
22 | - Handling data and repository patterns
23 | - Datasources
24 | - Isar
25 | - [Read Part 3 on Medium](https://medium.com/@enesakbal00/flutter-clean-architecture-part-3-data-layer-d9c5c63dc767)
26 | #### Part 4: The Presentation Layer
27 | - Designing the UI and managing state
28 | - BLoC Pattern
29 | - [Read Part 4 on Medium](https://medium.com/@enesakbal00/flutter-clean-architecture-part-4-presentation-layer-ba51445fad83)
30 | #### Part 5: Testing Each Layer
31 | - Implementing tests for robust and reliable code
32 | - Unit Testing
33 | - [Read Part 5 on Medium](https://medium.com/@enesakbal00/flutter-clean-architecture-part-5-unit-test-a1bb7791899f)
34 |
35 | ---
36 |
37 | ### UI Design
38 | -
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | https://github.com/enesakbal/Flutter-Clean-Architecture-Example/assets/60822023/bbe8f4e6-e460-47fc-9893-fa36881df65e
51 |
52 |
53 | ---
54 |
55 | ### Set Up
56 | - Clone Project
57 | ```bash
58 | git clone https://github.com/enesakbal/Flutter-Clean-Architecture-Example.git
59 | ```
60 |
61 | - Sign up [TMDB](https://developer.themoviedb.org/docs) and don't forget to replace the ```API_TOKEN``` value in the .env file with your own.
62 |
63 | - ```flutter run```
64 |
65 | ---
66 |
67 |
68 | ## Contact Me
69 | [](https://www.linkedin.com/in/enesakbl/)
70 | [](https://medium.com/@enesakbal00)
71 |
72 | enesakbal00@gmail.com
73 |
74 |
75 |
76 | created by ea.
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/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.example.flutter_clean_architecture"
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.example.flutter_clean_architecture"
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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
28 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/flutter_clean_architecture/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.flutter_clean_architecture
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/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/icons/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/icons/imdb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/instagram.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/icons/tiktok.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/icons/youtube.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 | 12.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, '12.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/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - isar_flutter_libs (1.0.0):
4 | - Flutter
5 | - path_provider_foundation (0.0.1):
6 | - Flutter
7 | - FlutterMacOS
8 | - sqflite_darwin (0.0.4):
9 | - Flutter
10 | - FlutterMacOS
11 | - url_launcher_ios (0.0.1):
12 | - Flutter
13 |
14 | DEPENDENCIES:
15 | - Flutter (from `Flutter`)
16 | - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
17 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
18 | - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
19 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
20 |
21 | EXTERNAL SOURCES:
22 | Flutter:
23 | :path: Flutter
24 | isar_flutter_libs:
25 | :path: ".symlinks/plugins/isar_flutter_libs/ios"
26 | path_provider_foundation:
27 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
28 | sqflite_darwin:
29 | :path: ".symlinks/plugins/sqflite_darwin/darwin"
30 | url_launcher_ios:
31 | :path: ".symlinks/plugins/url_launcher_ios/ios"
32 |
33 | SPEC CHECKSUMS:
34 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
35 | isar_flutter_libs: 9fc2cfb928c539e1b76c481ba5d143d556d94920
36 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
37 | sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
38 | url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
39 |
40 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
41 |
42 | COCOAPODS: 1.16.2
43 |
--------------------------------------------------------------------------------
/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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/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 | @main
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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/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/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesakbal/Flutter-Clean-Architecture-Example/f28a5a44f4df90c1b27d36816ea30d049a0bfa6e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@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/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSApplicationQueriesSchemes
6 |
7 | sms
8 | tel
9 |
10 | CFBundleDevelopmentRegion
11 | $(DEVELOPMENT_LANGUAGE)
12 | CFBundleDisplayName
13 | Flutter Clean Architecture
14 | CFBundleExecutable
15 | $(EXECUTABLE_NAME)
16 | CFBundleIdentifier
17 | $(PRODUCT_BUNDLE_IDENTIFIER)
18 | CFBundleInfoDictionaryVersion
19 | 6.0
20 | CFBundleName
21 | flutter_clean_architecture
22 | CFBundlePackageType
23 | APPL
24 | CFBundleShortVersionString
25 | $(FLUTTER_BUILD_NAME)
26 | CFBundleSignature
27 | ????
28 | CFBundleVersion
29 | $(FLUTTER_BUILD_NUMBER)
30 | LSRequiresIPhoneOS
31 |
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UIMainStoryboardFile
35 | Main
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | CADisableMinimumFrameDurationOnPhone
50 |
51 | UIApplicationSupportsIndirectInputEvents
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/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/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:flutter_dotenv/flutter_dotenv.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 | import 'package:get_it/get_it.dart';
7 | import 'package:hydrated_bloc/hydrated_bloc.dart';
8 | import 'package:path_provider/path_provider.dart';
9 |
10 | import 'src/config/router/app_router.dart';
11 | import 'src/core/database/local_database.dart';
12 | import 'src/core/network/dio_client.dart';
13 | import 'src/core/theme/app_theme.dart';
14 | import 'src/core/theme/cubit/theme_cubit.dart';
15 | import 'src/data/datasources/export_datasources.dart';
16 | import 'src/data/repositories/export_repository_impls.dart';
17 | import 'src/domain/repositories/actor/actor_repository.dart';
18 | import 'src/domain/repositories/movie/movie_repository.dart';
19 | import 'src/domain/usecases/export_usecases.dart';
20 | import 'src/presentation/cubit/actor/export_actor_cubits.dart';
21 | import 'src/presentation/cubit/movie/export_movie_cubits.dart';
22 | import 'src/presentation/cubit/movie/get_movie_credits/get_movie_credits_cubit.dart';
23 |
24 | part './src/injector.dart';
25 |
26 | final router = AppRouter();
27 |
28 | void main() async {
29 | WidgetsFlutterBinding.ensureInitialized();
30 | await dotenv.load();
31 | await init();
32 |
33 | await injector().initialize();
34 |
35 | final directory = HydratedStorageDirectory((await getApplicationDocumentsDirectory()).path);
36 |
37 | HydratedBloc.storage = await HydratedStorage.build(storageDirectory: directory);
38 |
39 | runApp(const MainApp());
40 | }
41 |
42 | class MainApp extends StatelessWidget {
43 | const MainApp({super.key});
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return MultiBlocProvider(
48 | providers: [
49 | BlocProvider(create: (context) => injector()),
50 | BlocProvider(create: (context) => injector()),
51 | BlocProvider(create: (context) => injector()..getSavedMovieDetails()),
52 | ],
53 | child: ScreenUtilInit(
54 | builder: (context, child) {
55 | return BlocBuilder(
56 | builder: (context, themeState) {
57 | return MaterialApp.router(
58 | themeMode: themeState.themeMode,
59 | theme: AppTheme.lightTheme,
60 | darkTheme: AppTheme.darkTheme,
61 | routerDelegate: AutoRouterDelegate(router),
62 | routeInformationParser: router.defaultRouteParser(),
63 | debugShowCheckedModeBanner: false,
64 | );
65 | },
66 | );
67 | },
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/src/config/gen/assets.gen.dart:
--------------------------------------------------------------------------------
1 | /// GENERATED CODE - DO NOT MODIFY BY HAND
2 | /// *****************************************************
3 | /// FlutterGen
4 | /// *****************************************************
5 |
6 | // coverage:ignore-file
7 | // ignore_for_file: type=lint
8 | // ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
9 |
10 | import 'package:flutter/widgets.dart';
11 | import 'package:flutter_svg/flutter_svg.dart';
12 | import 'package:flutter/services.dart';
13 |
14 | class $AssetsIconsGen {
15 | const $AssetsIconsGen();
16 |
17 | /// File path: assets/icons/facebook.svg
18 | SvgGenImage get facebook => const SvgGenImage('assets/icons/facebook.svg');
19 |
20 | /// File path: assets/icons/imdb.svg
21 | SvgGenImage get imdb => const SvgGenImage('assets/icons/imdb.svg');
22 |
23 | /// File path: assets/icons/instagram.svg
24 | SvgGenImage get instagram => const SvgGenImage('assets/icons/instagram.svg');
25 |
26 | /// File path: assets/icons/tiktok.svg
27 | SvgGenImage get tiktok => const SvgGenImage('assets/icons/tiktok.svg');
28 |
29 | /// File path: assets/icons/twitter.svg
30 | SvgGenImage get twitter => const SvgGenImage('assets/icons/twitter.svg');
31 |
32 | /// File path: assets/icons/youtube.svg
33 | SvgGenImage get youtube => const SvgGenImage('assets/icons/youtube.svg');
34 |
35 | /// List of all assets
36 | List get values =>
37 | [facebook, imdb, instagram, tiktok, twitter, youtube];
38 | }
39 |
40 | class Assets {
41 | Assets._();
42 |
43 | static const $AssetsIconsGen icons = $AssetsIconsGen();
44 | }
45 |
46 | class SvgGenImage {
47 | const SvgGenImage(this._assetName);
48 |
49 | final String _assetName;
50 |
51 | SvgPicture svg({
52 | Key? key,
53 | bool matchTextDirection = false,
54 | AssetBundle? bundle,
55 | String? package,
56 | double? width,
57 | double? height,
58 | BoxFit fit = BoxFit.contain,
59 | AlignmentGeometry alignment = Alignment.center,
60 | bool allowDrawingOutsideViewBox = false,
61 | WidgetBuilder? placeholderBuilder,
62 | String? semanticsLabel,
63 | bool excludeFromSemantics = false,
64 | SvgTheme theme = const SvgTheme(),
65 | ColorFilter? colorFilter,
66 | Clip clipBehavior = Clip.hardEdge,
67 | @deprecated Color? color,
68 | @deprecated BlendMode colorBlendMode = BlendMode.srcIn,
69 | @deprecated bool cacheColorFilter = false,
70 | }) {
71 | return SvgPicture.asset(
72 | _assetName,
73 | key: key,
74 | matchTextDirection: matchTextDirection,
75 | bundle: bundle,
76 | package: package,
77 | width: width,
78 | height: height,
79 | fit: fit,
80 | alignment: alignment,
81 | allowDrawingOutsideViewBox: allowDrawingOutsideViewBox,
82 | placeholderBuilder: placeholderBuilder,
83 | semanticsLabel: semanticsLabel,
84 | excludeFromSemantics: excludeFromSemantics,
85 | theme: theme,
86 | colorFilter: colorFilter,
87 | color: color,
88 | colorBlendMode: colorBlendMode,
89 | clipBehavior: clipBehavior,
90 | cacheColorFilter: cacheColorFilter,
91 | );
92 | }
93 |
94 | String get path => _assetName;
95 |
96 | String get keyName => _assetName;
97 | }
98 |
--------------------------------------------------------------------------------
/lib/src/config/router/app_router.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import '../../core/constants/path_constants.dart';
5 | import 'app_router.gr.dart';
6 |
7 | @AutoRouterConfig(replaceInRouteName: 'View|Widget,Route')
8 |
9 | /// Holds all the routes that are defined in the app
10 | /// Used to generate the Router object
11 | final class AppRouter extends $AppRouter {
12 | AppRouter();
13 |
14 | @override
15 | List get routes => [
16 | AdaptiveRoute(
17 | page: MasterRoute.page,
18 | path: PathConstants.master,
19 | initial: true,
20 | children: [
21 | AdaptiveRoute(
22 | page: MoviesRoute.page,
23 | path: PathConstants.movies,
24 | title: (_, __) => 'Movies',
25 | ),
26 | AdaptiveRoute(
27 | page: BookmarksRoute.page,
28 | path: PathConstants.bookmarks,
29 | title: (_, __) => 'Bookmarks',
30 | ),
31 | ],
32 | ),
33 | CustomRoute(
34 | page: MovieDetailRoute.page,
35 | path: PathConstants.movieDetail,
36 | durationInMilliseconds: 800,
37 | reverseDurationInMilliseconds: 800,
38 | transitionsBuilder: (context, animation, secondaryAnimation, child) {
39 | return FadeTransition(
40 | opacity: animation,
41 | child: child,
42 | );
43 | },
44 | ),
45 | ];
46 | }
47 |
--------------------------------------------------------------------------------
/lib/src/core/components/bottom_sheet/_mixin/base_bottom_sheet_mixin.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | mixin BaseBottomSheetMixin on StatelessWidget {
5 | Future show(BuildContext context) {
6 | return showModalBottomSheet(
7 | context: context,
8 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: const Radius.circular(20).r)),
9 | isScrollControlled: true,
10 | builder: (context) {
11 | return Padding(
12 | padding: EdgeInsets.fromLTRB(
13 | 0.05.sw,
14 | 0.05.sw,
15 | 0.05.sw,
16 | 0,
17 | ).r,
18 | child: Column(
19 | mainAxisSize: MainAxisSize.min,
20 | children: [
21 | //* Drag Indicator
22 | Divider(
23 | thickness: 2.h,
24 | endIndent: 0.35.sw,
25 | indent: 0.35.sw,
26 | color: Theme.of(context).disabledColor,
27 | ),
28 |
29 | 20.verticalSpace,
30 |
31 | //* Content
32 | this,
33 | ],
34 | ),
35 | );
36 | },
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/core/components/bottom_sheet/social_media_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:get_it/get_it.dart';
5 |
6 | import '../../../presentation/cubit/actor/export_actor_cubits.dart';
7 | import '../buttons/retry_button.dart';
8 | import '../buttons/social_button.dart';
9 | import '../indicator/base_indicator.dart';
10 | import '_mixin/base_bottom_sheet_mixin.dart';
11 |
12 | class SocialMediaBottomSheet extends StatelessWidget with BaseBottomSheetMixin {
13 | const SocialMediaBottomSheet({super.key, required this.actorId, required this.actorName});
14 |
15 | final String? actorId;
16 | final String? actorName;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return BlocProvider(
21 | create: (context) => GetIt.I()..getActorSocialMedia(actorId: actorId ?? '0'),
22 | child: _SocialMediaBottomSheet(actorId ?? '0', actorName: actorName),
23 | );
24 | }
25 | }
26 |
27 | class _SocialMediaBottomSheet extends StatelessWidget {
28 | const _SocialMediaBottomSheet(this.actorId, {required this.actorName});
29 | final String actorId;
30 | final String? actorName;
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return SafeArea(
35 | child: SizedBox(
36 | width: 1.sw,
37 | height: 0.25.sh,
38 | child: BlocBuilder(
39 | builder: (context, state) {
40 | if (state is GetActorSocialMediaError) {
41 | return RetryButton(
42 | text: state.message,
43 | retryAction: () => context.read().getActorSocialMedia(actorId: actorId),
44 | );
45 | }
46 |
47 | if (state is! GetActorSocialMediaLoaded) return const BaseIndicator();
48 |
49 | final list = [
50 | state.instagramId,
51 | state.twitterId,
52 | state.facebookId,
53 | state.youtubeId,
54 | state.imdbId,
55 | state.tiktokId,
56 | ];
57 |
58 | if (list.every((element) => element == null)) {
59 | return const Center(child: Text('Social media account not found'));
60 | }
61 |
62 | return Column(
63 | crossAxisAlignment: CrossAxisAlignment.start,
64 | children: [
65 | Text(
66 | "$actorName's social accounts",
67 | style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
68 | ),
69 | const Divider(),
70 | Expanded(
71 | child: Row(
72 | children: [
73 | if (list[0] != null) Expanded(child: SocialButton.instagram(id: state.instagramId)),
74 | if (list[1] != null) Expanded(child: SocialButton.twitter(id: state.twitterId)),
75 | if (list[2] != null) Expanded(child: SocialButton.facebook(id: state.facebookId)),
76 | if (list[3] != null) Expanded(child: SocialButton.youtube(id: state.youtubeId)),
77 | if (list[4] != null) Expanded(child: SocialButton.imdb(id: state.imdbId)),
78 | if (list[5] != null) Expanded(child: SocialButton.tiktok(id: state.tiktokId)),
79 | ],
80 | ),
81 | ),
82 | ],
83 | );
84 | },
85 | ),
86 | ),
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/src/core/components/buttons/bookmark_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:get_it/get_it.dart';
5 |
6 | import '../../../domain/entities/export_entities.dart';
7 | import '../../../presentation/cubit/movie/get_saved_movies/get_saved_movies_cubit.dart';
8 | import '../../../presentation/cubit/movie/toggle_bookmark/toggle_bookmark_cubit.dart';
9 |
10 | class BookmarkButton extends StatelessWidget {
11 | const BookmarkButton({super.key, required this.movieDetailEntity}) : _buttonStyle = null;
12 |
13 | BookmarkButton.filled({super.key, required this.movieDetailEntity})
14 | : _buttonStyle = ButtonStyle(
15 | backgroundColor: MaterialStateProperty.all(Colors.white),
16 | shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8).r)),
17 | elevation: MaterialStateProperty.all(1.5),
18 | shadowColor: MaterialStateProperty.all(Colors.black87),
19 | );
20 |
21 | final MovieDetailEntity? movieDetailEntity;
22 |
23 | final ButtonStyle? _buttonStyle;
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return BlocBuilder(
28 | builder: (_, getSavedMoviesState) {
29 | if (getSavedMoviesState is GetSavedMoviesLoaded) {
30 | bool isBookmarked =
31 | getSavedMoviesState.movies?.any((element) => element.id == movieDetailEntity?.id) ?? false;
32 |
33 | return IconButton(
34 | style: _buttonStyle,
35 | padding: EdgeInsets.zero,
36 | onPressed: () async {
37 | await GetIt.I()
38 | .toggleBookmark(movieDetailEntity: movieDetailEntity)
39 | .then((_) => context.read().getSavedMovieDetails());
40 |
41 | isBookmarked = !isBookmarked;
42 | },
43 | icon: AnimatedCrossFade(
44 | firstChild: Icon(
45 | Icons.bookmark,
46 | size: 30,
47 | color: Theme.of(context).primaryColor,
48 | ),
49 | secondChild: Icon(
50 | Icons.bookmark_border,
51 | size: 30,
52 | color: Theme.of(context).primaryColor,
53 | ),
54 | crossFadeState: isBookmarked ? CrossFadeState.showFirst : CrossFadeState.showSecond,
55 | duration: kThemeAnimationDuration,
56 | ),
57 | );
58 | }
59 |
60 | return const SizedBox();
61 | },
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/src/core/components/buttons/retry_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RetryButton extends StatelessWidget {
4 | const RetryButton({super.key, required this.retryAction, required this.text});
5 |
6 | final void Function() retryAction;
7 | final String text;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Column(
12 | children: [
13 | Text(
14 | text,
15 | style: Theme.of(context).textTheme.titleLarge,
16 | ),
17 | const Divider(),
18 | Expanded(
19 | child: Center(
20 | child: TextButton(
21 | onPressed: () => retryAction.call(),
22 | child: const Text('Retry'),
23 | ),
24 | ),
25 | ),
26 | ],
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/src/core/components/buttons/social_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../../config/gen/assets.gen.dart';
4 | import '../../utils/url_launcher_manager.dart';
5 |
6 | class SocialButton extends StatelessWidget {
7 | final Widget icon;
8 | final void Function() onPressed;
9 | final String? id;
10 |
11 | SocialButton.instagram({super.key, required this.id})
12 | : icon = Assets.icons.instagram.svg(height: 60),
13 | onPressed = (() async => UrlLauncherManager.redirectToInstagramById(id ?? ''));
14 |
15 | SocialButton.twitter({super.key, required this.id})
16 | : icon = Assets.icons.twitter.svg(height: 60),
17 | onPressed = (() async => UrlLauncherManager.redirectToTwitterById(id ?? ''));
18 |
19 | SocialButton.facebook({super.key, required this.id})
20 | : icon = Assets.icons.facebook.svg(height: 60),
21 | onPressed = (() async => UrlLauncherManager.redirectToFacebookById(id ?? ''));
22 |
23 | SocialButton.youtube({super.key, required this.id})
24 | : icon = Assets.icons.youtube.svg(height: 60),
25 | onPressed = (() async => UrlLauncherManager.redirectToYoutubeById(id ?? ''));
26 |
27 | SocialButton.imdb({super.key, required this.id})
28 | : icon = Assets.icons.imdb.svg(height: 60),
29 | onPressed = (() async => UrlLauncherManager.redirectToImdbById(id));
30 |
31 | SocialButton.tiktok({super.key, required this.id})
32 | : icon = Assets.icons.tiktok.svg(height: 60),
33 | onPressed = (() async => UrlLauncherManager.redirectToTiktokById(id));
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return IconButton(onPressed: onPressed, icon: icon);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/core/components/card/movie_card.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_bloc/flutter_bloc.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 |
7 | import '../../../domain/entities/export_entities.dart';
8 | import '../../../presentation/cubit/movie/export_movie_cubits.dart';
9 | import '../buttons/bookmark_button.dart';
10 | import '../image/base_network_image.dart';
11 |
12 | class MovieCard extends StatelessWidget {
13 | const MovieCard({super.key, this.movie});
14 | final MovieDetailEntity? movie;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Container(
19 | margin: const EdgeInsets.all(8),
20 | child: Stack(
21 | children: [
22 | BaseNetworkImage.originalImageSize(movie?.posterPath),
23 | Column(
24 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
25 | children: [
26 | Align(
27 | alignment: Alignment.topRight,
28 | child: BlocBuilder(
29 | builder: (context, state) {
30 | if (state is GetSavedMoviesLoaded) {
31 | return BookmarkButton(
32 | movieDetailEntity: movie,
33 | );
34 | }
35 |
36 | return const SizedBox.shrink();
37 | },
38 | ),
39 | ),
40 | ClipRRect(
41 | borderRadius: const BorderRadius.only(
42 | bottomLeft: Radius.circular(8),
43 | bottomRight: Radius.circular(8),
44 | ),
45 | child: BackdropFilter(
46 | filter: ImageFilter.blur(sigmaX: 5, sigmaY: 10),
47 | child: Container(
48 | alignment: Alignment.centerLeft,
49 | width: 1.sw,
50 | height: 60,
51 | padding: const EdgeInsets.all(4),
52 | decoration: BoxDecoration(
53 | gradient: LinearGradient(
54 | colors: [Colors.black.withOpacity(0.8), Colors.black.withOpacity(0.0)],
55 | begin: Alignment.bottomCenter,
56 | end: Alignment.topCenter,
57 | ),
58 | ),
59 | child: Text(
60 | movie?.title ?? '',
61 | maxLines: 2,
62 | overflow: TextOverflow.ellipsis,
63 | style: Theme.of(context)
64 | .textTheme
65 | .titleMedium
66 | ?.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
67 | ),
68 | ),
69 | ),
70 | ),
71 | ],
72 | )
73 | ],
74 | ),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/src/core/components/image/base_network_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import '../../constants/image_constants.dart';
5 | import '../indicator/base_indicator.dart';
6 |
7 | class BaseNetworkImage extends StatelessWidget {
8 | const BaseNetworkImage(this.url, {super.key, this.hasRadius = true});
9 |
10 | BaseNetworkImage.originalImageSize(String? targetUrl, {super.key, this.hasRadius = true})
11 | : url = ImageConstants.originalImage(targetUrl);
12 |
13 | final String? url;
14 | final bool hasRadius;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return CachedNetworkImage(
19 | imageUrl: url ?? '',
20 | progressIndicatorBuilder: (context, url, progress) => const BaseIndicator(),
21 | errorWidget: (_, __, ___) => const BaseIndicator(),
22 | imageBuilder: (context, imageProvider) {
23 | return Container(
24 | decoration: BoxDecoration(
25 | borderRadius: hasRadius ? BorderRadius.circular(8) : null,
26 | image: DecorationImage(
27 | image: imageProvider,
28 | fit: BoxFit.cover,
29 | ),
30 | ),
31 | );
32 | },
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/core/components/indicator/base_indicator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class BaseIndicator extends StatelessWidget {
5 | const BaseIndicator({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Center(
10 | child: CircularProgressIndicator.adaptive(
11 | strokeWidth: 2.5.r,
12 | ),
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/core/constants/app_constants.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_dotenv/flutter_dotenv.dart';
2 |
3 | class AppConstants {
4 | static final baseUrl = '${dotenv.env['BASE_URL']}'; //* https://api.themoviedb.org/3
5 | static final apiToken = '${dotenv.env['API_TOKEN']}'; //* your TMDB token
6 | }
7 |
--------------------------------------------------------------------------------
/lib/src/core/constants/image_constants.dart:
--------------------------------------------------------------------------------
1 | class ImageConstants {
2 | static String? originalImage(String? posterPath) => 'https://image.tmdb.org/t/p/original$posterPath';
3 | }
4 |
--------------------------------------------------------------------------------
/lib/src/core/constants/path_constants.dart:
--------------------------------------------------------------------------------
1 | class PathConstants {
2 | //* Master
3 | static const String master = '/master';
4 | static const String movies = 'movies';
5 | static const String bookmarks = 'bookmarks';
6 |
7 | //* Detail
8 | static const String movieDetail = '/movie_detail';
9 |
10 | //* More..
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/core/constants/url_constants.dart:
--------------------------------------------------------------------------------
1 | class UrlConstants {
2 | //* Movie
3 | static const popularMovies = '/movie/popular';
4 | static const topRatedMovies = '/movie/top_rated';
5 | static const movieCredits = '/movie/{movie_id}/credits';
6 |
7 | //* Actor
8 | static const actorDetail = '/person/{person_id}';
9 | static const actorSocialMedia = '/person/{person_id}/external_ids';
10 | }
11 |
--------------------------------------------------------------------------------
/lib/src/core/database/local_database.dart:
--------------------------------------------------------------------------------
1 | import 'package:isar/isar.dart';
2 | import 'package:path_provider/path_provider.dart';
3 |
4 | import '../../data/datasources/local/_collections/movie_detail/movie_detail_collection.dart';
5 | /// A class representing a local database.
6 | ///
7 | /// This class provides methods to initialize and access the Isar database.
8 | class LocalDatabase {
9 | late final Isar _isar;
10 | bool _isInitialized = false;
11 |
12 | /// Returns the initialized Isar database instance.
13 | ///
14 | /// Throws an [IsarError] if the database has not been initialized.
15 | Isar get db => _isInitialized ? _isar : throw IsarError('Isar has not been initialized.');
16 |
17 | /// Initializes the Isar database.
18 | ///
19 | /// Throws an [IsarError] if the database has already been initialized.
20 | Future initialize() async {
21 | if (_isInitialized) throw IsarError('Isar has already been initialized.');
22 |
23 | final directory = await getApplicationDocumentsDirectory();
24 | _isar = await Isar.open([MovieDetailCollectionSchema], directory: directory.path);
25 |
26 | _isInitialized = true;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/core/exceptions/database/database_exception.dart:
--------------------------------------------------------------------------------
1 | import 'package:equatable/equatable.dart';
2 | import 'package:isar/isar.dart';
3 | /// A custom exception class for database errors.
4 | ///
5 | /// This class extends the `Equatable` and `Exception` classes.
6 | ///
7 | /// The `message` property is a string that holds the error message.
8 | ///
9 | /// The `DatabaseException.fromIsarError` constructor takes an `IsarError` object
10 | /// and initializes the `message` property with the error message from the `IsarError`.
11 | ///
12 | /// The `props` getter returns a list containing the `message` property.
13 | class DatabaseException extends Equatable implements Exception {
14 | late final String message;
15 |
16 | DatabaseException.fromIsarError(IsarError isarError) : message = isarError.message;
17 |
18 | @override
19 | List