diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..57d9448a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Log/OS Files
+*.log
+
+# Android Studio generated files and folders
+captures/
+.externalNativeBuild/
+.cxx/
+*.apk
+output.json
+
+# IntelliJ
+*.iml
+.idea/
+
+# Keystore files
+*.jks
+*.keystore
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Android Profiling
+*.hprof
+/.gradle/
+/.idea/
diff --git a/.gradle/7.0.2/dependencies-accessors/dependencies-accessors.lock b/.gradle/7.0.2/dependencies-accessors/dependencies-accessors.lock
new file mode 100644
index 00000000..0c430279
Binary files /dev/null and b/.gradle/7.0.2/dependencies-accessors/dependencies-accessors.lock differ
diff --git a/.gradle/7.0.2/dependencies-accessors/gc.properties b/.gradle/7.0.2/dependencies-accessors/gc.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/.gradle/7.0.2/executionHistory/executionHistory.bin b/.gradle/7.0.2/executionHistory/executionHistory.bin
new file mode 100644
index 00000000..7a43b3bd
Binary files /dev/null and b/.gradle/7.0.2/executionHistory/executionHistory.bin differ
diff --git a/.gradle/7.0.2/executionHistory/executionHistory.lock b/.gradle/7.0.2/executionHistory/executionHistory.lock
new file mode 100644
index 00000000..fe63f976
Binary files /dev/null and b/.gradle/7.0.2/executionHistory/executionHistory.lock differ
diff --git a/.gradle/7.0.2/fileChanges/last-build.bin b/.gradle/7.0.2/fileChanges/last-build.bin
new file mode 100644
index 00000000..f76dd238
Binary files /dev/null and b/.gradle/7.0.2/fileChanges/last-build.bin differ
diff --git a/.gradle/7.0.2/fileHashes/fileHashes.bin b/.gradle/7.0.2/fileHashes/fileHashes.bin
new file mode 100644
index 00000000..77184834
Binary files /dev/null and b/.gradle/7.0.2/fileHashes/fileHashes.bin differ
diff --git a/.gradle/7.0.2/fileHashes/fileHashes.lock b/.gradle/7.0.2/fileHashes/fileHashes.lock
new file mode 100644
index 00000000..d8c0bea8
Binary files /dev/null and b/.gradle/7.0.2/fileHashes/fileHashes.lock differ
diff --git a/.gradle/7.0.2/fileHashes/resourceHashesCache.bin b/.gradle/7.0.2/fileHashes/resourceHashesCache.bin
new file mode 100644
index 00000000..2377de19
Binary files /dev/null and b/.gradle/7.0.2/fileHashes/resourceHashesCache.bin differ
diff --git a/.gradle/7.0.2/gc.properties b/.gradle/7.0.2/gc.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/.gradle/7.0.2/javaCompile/classAnalysis.bin b/.gradle/7.0.2/javaCompile/classAnalysis.bin
new file mode 100644
index 00000000..fae2e43c
Binary files /dev/null and b/.gradle/7.0.2/javaCompile/classAnalysis.bin differ
diff --git a/.gradle/7.0.2/javaCompile/jarAnalysis.bin b/.gradle/7.0.2/javaCompile/jarAnalysis.bin
new file mode 100644
index 00000000..b9977f3b
Binary files /dev/null and b/.gradle/7.0.2/javaCompile/jarAnalysis.bin differ
diff --git a/.gradle/7.0.2/javaCompile/javaCompile.lock b/.gradle/7.0.2/javaCompile/javaCompile.lock
new file mode 100644
index 00000000..0b89bcca
Binary files /dev/null and b/.gradle/7.0.2/javaCompile/javaCompile.lock differ
diff --git a/.gradle/7.0.2/javaCompile/taskHistory.bin b/.gradle/7.0.2/javaCompile/taskHistory.bin
new file mode 100644
index 00000000..76b9cbbf
Binary files /dev/null and b/.gradle/7.0.2/javaCompile/taskHistory.bin differ
diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 00000000..4fa35361
Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 00000000..83c4e86b
--- /dev/null
+++ b/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Mon Dec 20 22:03:19 IRST 2021
+gradle.version=7.0.2
diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 00000000..57185882
Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/.gradle/checksums/checksums.lock b/.gradle/checksums/checksums.lock
new file mode 100644
index 00000000..dc641c7e
Binary files /dev/null and b/.gradle/checksums/checksums.lock differ
diff --git a/.gradle/checksums/md5-checksums.bin b/.gradle/checksums/md5-checksums.bin
new file mode 100644
index 00000000..2ed80a9d
Binary files /dev/null and b/.gradle/checksums/md5-checksums.bin differ
diff --git a/.gradle/checksums/sha1-checksums.bin b/.gradle/checksums/sha1-checksums.bin
new file mode 100644
index 00000000..ddc867d8
Binary files /dev/null and b/.gradle/checksums/sha1-checksums.bin differ
diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/README.md b/README.md
index 8bb0c38d..26c28422 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,20 @@
-# Work sample - Application developer
+# Daresay Movie App
-## Assignment
-- Build an awesome movie app that shows popular and high rated movies.
-- Code it for the platform (Android, iOS, web) you applied for or the one you prefer.
+
+
+
+
+
+
+
-## Requirements
-- Use an open https://developers.themoviedb.org/open source API. Please read the API [Authentication section](https://developers.themoviedb.org/3/getting-started/authentication) to get started.
-- Discover most popular and highly rated movies.
-- Display the movies with creative look and feel of an app to meet design guidelines for your platform (Material Design etc.).
-- Launch a detail screen whenever a particular movie is selected.
+## What did I Do
-## Examples of bonus features
-
-- Allow user to save a favorite movie for offline access.
-- Allow user to read movie reviews.
-
-## We expect you to
-
-- Write clean code.
-- Create a responsive design.
-- Handle error cases.
-- Use the latest libraries and technologies.
-- Tested code is a big plus.
-
-### User experience
-
-The features of the app might be few, but we expect you to deliver a solution with a high user experience. Imagine this application to be used by real users, with real needs. Make it interesting, fun and intuitive to use. And of course you are allowed to extend your applications functionality.
-
-### Code
-
-We expect that the code is of high quality and under source control. Expect the solution to be continuously worked on by other developers and should therefore be easy to understand, adjust and extend. True beauty starts on the inside!
-
-## Delivery
-
-Fork the repository, code in your fork and make a pull request when done. A nice commit history describing your work is preferred over squashing it into one commit.
-Also send us an e-mail to let us know!
-
-### Good luck!
-
----
+- Movie App using MVVM Architecture
+- Main Fragment for showing four Categories of Movies
+- DetailsFragment for showing movie Details like Director, budget, list of Cast and Posters and even Reviews!
+- Profile fragment fow showing Users most favorite Movies depends on his Tastes.
+- Fully Internet base Application.
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..6bbf1ed0
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,262 @@
+plugins {
+ id 'com.android.application'
+ id 'dagger.hilt.android.plugin'
+ id 'kotlin-android'
+ id 'kotlin-kapt'
+ id 'kotlin-parcelize'
+ id("io.gitlab.arturbosch.detekt") version "1.18.1"
+ id("org.jlleitschuh.gradle.ktlint") version "10.2.0"
+}
+
+android {
+ compileSdk 31
+ buildToolsVersion '30.0.3'
+
+ defaultConfig {
+ applicationId "com.arashafsharpour.daresaymovie"
+ minSdk 21
+ targetSdk 31
+ versionCode 1
+ versionName "1.0"
+ multiDexEnabled = true
+ vectorDrawables.useSupportLibrary = true
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments += ["room.schemaLocation" : "$projectDir/schemas".toString()]
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+
+ lintOptions {
+ disable 'TypographyFractions','TypographyQuotes'
+ enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
+ checkOnly 'NewApi', 'InlinedApi'
+ quiet true
+ abortOnError true
+ ignoreWarnings true
+ }
+
+ ktlint {
+ debug.set(false)
+ version.set("10.2.0")
+ verbose.set(true)
+ android.set(true)
+ outputToConsole.set(true)
+ ignoreFailures.set(false)
+ enableExperimentalRules.set(true)
+ filter {
+ exclude("**/generated/**")
+ include("**/kotlin/**")
+ }
+ }
+
+ detekt {
+ config = rootProject.files("config/detekt/detekt.yml")
+ reports {
+ html {
+ enabled = true
+ destination = file("build/reports/detekt.html")
+ }
+ }
+ }
+
+
+
+
+ kotlinOptions {
+ jvmTarget = java_version.toString()
+ useIR = true
+ }
+ buildFeatures {
+ dataBinding true
+ }
+
+ packagingOptions {
+ exclude 'META-INF/DEPENDENCIES'
+ exclude 'META-INF/LICENSE'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/license.txt'
+ exclude 'META-INF/NOTICE'
+ exclude 'META-INF/NOTICE.txt'
+ exclude 'META-INF/notice.txt'
+ exclude 'META-INF/ASL2.0'
+ exclude("META-INF/*.kotlin_module")
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
+ kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.time.ExperimentalTime"]
+ kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"]
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
+ def room_version = "2.3.0"
+
+ implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation 'androidx.appcompat:appcompat:1.4.0'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
+ implementation "androidx.fragment:fragment-ktx:1.4.0"
+
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.7.0'
+
+ // Room and Architectural Components
+ implementation "androidx.room:room-ktx:$room_version"
+ implementation "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.legacy:legacy-support-v4:1.0.0"
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
+ implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0"
+ testImplementation "androidx.arch.core:core-testing:2.1.0"
+
+ // binding
+ implementation ("com.github.skydoves:bindables:1.0.9") {
+ exclude group: "com.google.android.material"
+ }
+
+ //Hilt
+ implementation "com.google.dagger:hilt-android:$hilt_version"
+ kapt "com.google.dagger:hilt-compiler:$hilt_version"
+
+ implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
+ kapt "androidx.hilt:hilt-compiler:1.0.0"
+
+ implementation 'androidx.annotation:annotation:1.3.0'
+ implementation 'androidx.multidex:multidex:2.0.1'
+
+ // Architectural Components
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
+
+
+ //Network
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+ implementation "com.squareup.okhttp3:logging-interceptor:4.9.0"
+ testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
+ api 'com.fasterxml.jackson.core:jackson-core:2.12.3'
+ api 'com.squareup.retrofit2:converter-jackson:2.9.0'
+ api "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3"
+
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ // Coroutines
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
+
+ // Coroutine Lifecycle Scopes
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
+
+ // Navigation Components
+ implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
+ implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
+
+ // Glide
+ implementation 'com.github.bumptech.glide:glide:4.12.0'
+ kapt 'com.github.bumptech.glide:compiler:4.12.0'
+ implementation 'jp.wasabeef:glide-transformations:4.3.0'
+
+ // Dagger Core
+ implementation "com.google.dagger:dagger:2.40.1"
+ kapt "com.google.dagger:dagger-compiler:2.40.1"
+
+// // Dagger Android
+ api 'com.google.dagger:dagger-android:2.38.1'
+ api 'com.google.dagger:dagger-android-support:2.38.1'
+ kapt 'com.google.dagger:dagger-android-processor:2.38.1'
+
+ // Activity KTX for ViewModels()
+ implementation "androidx.activity:activity-ktx:1.4.0"
+
+
+ // For instrumentation tests
+ androidTestImplementation 'com.google.dagger:hilt-android-testing:2.38.1'
+ kaptAndroidTest 'com.google.dagger:hilt-compiler:2.40.1'
+
+ // For local unit tests
+ testImplementation "junit:junit:4.13.2"
+ testImplementation "androidx.test:core:1.4.0"
+ testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
+ testImplementation "org.mockito:mockito-inline:3.12.4"
+ testImplementation "app.cash.turbine:turbine:0.6.1"
+ testImplementation "org.robolectric:robolectric:4.6.1"
+ androidTestImplementation "com.google.truth:truth:1.1.3"
+ androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "com.android.support.test:runner:1.0.2"
+ androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
+
+ // Timber
+ implementation 'com.jakewharton.timber:timber:5.0.0'
+
+ //Transformation UI
+ implementation "com.github.skydoves:transformationlayout:1.1.0"
+
+ // gradation
+ implementation "com.github.skydoves:rainbow:1.0.3"
+
+ implementation 'android.arch.lifecycle:extensions:1.1.1'
+
+ //Jackson
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3"
+
+
+ // Import the BoM for the Firebase platform
+ implementation platform('com.google.firebase:firebase-bom:28.4.1')
+
+ // Declare the dependencies for the Firebase Cloud Messaging and Analytics libraries
+ // When using the BoM, you don't specify versions in Firebase library dependencies
+ implementation 'com.google.firebase:firebase-messaging-ktx:23.0.0'
+ implementation 'com.google.firebase:firebase-analytics-ktx:20.0.0'
+
+ //SharedPreferences
+ implementation "androidx.preference:preference-ktx:1.1.1"
+
+ //Window
+ implementation "androidx.window:window:1.0.0-beta04"
+
+ //Calligraphy
+ implementation 'io.github.inflationx:calligraphy3:3.1.1'
+ implementation 'io.github.inflationx:viewpump:2.0.3'
+
+ //Blurry
+ implementation 'jp.wasabeef:blurry:4.0.0'
+
+ //Lottie
+ implementation 'com.airbnb.android:lottie:4.2.1'
+
+ //Paging
+ implementation "androidx.paging:paging-runtime:3.1.0"
+
+ //Circle Image View
+ implementation 'de.hdodenhof:circleimageview:3.1.0'
+}
+
+kapt {
+ correctErrorTypes true
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/schemas/com.arashafsharpour.daresaymovie.persistence.database.roomdatabase.DaresayMovieDatabase/1.json b/app/schemas/com.arashafsharpour.daresaymovie.persistence.database.roomdatabase.DaresayMovieDatabase/1.json
new file mode 100644
index 00000000..63325d99
--- /dev/null
+++ b/app/schemas/com.arashafsharpour.daresaymovie.persistence.database.roomdatabase.DaresayMovieDatabase/1.json
@@ -0,0 +1,13 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "d41d8cd98f00b204e9800998ecf8427e",
+ "entities": [],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd41d8cd98f00b204e9800998ecf8427e')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/arashafsharpour/daresaymovie/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/arashafsharpour/daresaymovie/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..337e928e
--- /dev/null
+++ b/app/src/androidTest/java/com/arashafsharpour/daresaymovie/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.arashafsharpour.daresaymovie
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.arashafsharpour.daresaymovie", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..13f200ac
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 00000000..1dea5373
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/DaresayMovieApplication.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/DaresayMovieApplication.kt
new file mode 100644
index 00000000..cdc8d8c9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/DaresayMovieApplication.kt
@@ -0,0 +1,42 @@
+package com.arashafsharpour.daresaymovie
+
+import android.content.Context
+import androidx.multidex.BuildConfig
+import androidx.multidex.MultiDexApplication
+import dagger.hilt.android.HiltAndroidApp
+
+import io.github.inflationx.calligraphy3.CalligraphyConfig
+
+import io.github.inflationx.calligraphy3.CalligraphyInterceptor
+
+import io.github.inflationx.viewpump.ViewPump
+import io.github.inflationx.viewpump.ViewPumpContextWrapper
+import timber.log.Timber
+
+@HiltAndroidApp
+class DaresayMovieApplication : MultiDexApplication() {
+
+ override fun onCreate() {
+ super.onCreate()
+ ViewPump.init(
+ ViewPump.builder()
+ .addInterceptor(
+ CalligraphyInterceptor(
+ CalligraphyConfig.Builder()
+ .setDefaultFontPath("font/roboto_regular.ttf")
+ .setFontAttrId(R.font.roboto_regular)
+ .build()
+ )
+ ).setCustomViewInflationEnabled(true)
+ .build()
+ )
+
+
+ Timber.plant(Timber.DebugTree())
+
+ }
+
+ override fun attachBaseContext(newBase: Context) {
+ super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/IMainCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/IMainCoordinator.kt
new file mode 100644
index 00000000..f7e5a9d7
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/IMainCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.main
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface IMainCoordinator : ICoordinator
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivity.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivity.kt
new file mode 100644
index 00000000..d9b8021f
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivity.kt
@@ -0,0 +1,25 @@
+package com.arashafsharpour.daresaymovie.features.main
+
+import android.os.Bundle
+import androidx.activity.viewModels
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.ActivityMainBinding
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseActivity
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : BaseActivity() {
+
+ override val viewModel: MainActivityViewModel by viewModels()
+ override val layoutRes: Int = R.layout.activity_main
+ override val navigationId: Int = R.id.main_nav_host
+ override val hasNavigation: Boolean = true
+
+ override fun initializeBindingVariables() {
+ binding.vm = viewModel
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivityViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivityViewModel.kt
new file mode 100644
index 00000000..e1996e29
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainActivityViewModel.kt
@@ -0,0 +1,12 @@
+package com.arashafsharpour.daresaymovie.features.main
+
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class MainActivityViewModel @Inject constructor(
+ val coordinator: IMainCoordinator,
+ override val exceptionHandler: ExceptionHandler
+) : BaseViewModel(coordinator)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainCoordinator.kt
new file mode 100644
index 00000000..03c63ec9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/main/MainCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.main
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class MainCoordinator @Inject constructor() : Coordinator(), IMainCoordinator
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailFragment.kt
new file mode 100644
index 00000000..fec30f37
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailFragment.kt
@@ -0,0 +1,113 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.FragmentMovieDetailBinding
+import com.arashafsharpour.daresaymovie.features.moviedetail.adapter.CastAdapter
+import com.arashafsharpour.daresaymovie.features.moviedetail.adapter.GenreAdapter
+import com.arashafsharpour.daresaymovie.features.moviedetail.adapter.PostersAdapter
+import com.arashafsharpour.daresaymovie.features.reviews.ReviewsListDialogFragment
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseFragment
+import com.arashafsharpour.daresaymovie.persistence.MOVIE_REVIEW_DIALOG_TAG
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MovieDetailFragment : BaseFragment() {
+
+ @Inject
+ lateinit var genreAdapter: GenreAdapter
+ @Inject
+ lateinit var castAdapter: CastAdapter
+ @Inject
+ lateinit var postersAdapter: PostersAdapter
+ override val layoutResourceId: Int = R.layout.fragment_movie_detail
+ override val viewModel: MovieDetailViewModel by activityViewModels()
+
+ override fun initializeBindingVariables() {
+ binding.movie = viewModel
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.getString("id")?.let {
+ getMovieDetail(it)
+ getCredits(it)
+ getReviews(it)
+ getPosters(it)
+ }
+ }
+
+ private fun configBindingClickListeners() {
+ binding.readMoreText.setOnClickListener {
+ viewModel.movieDetail.value?.let {
+ if (parentFragmentManager.findFragmentByTag(MOVIE_REVIEW_DIALOG_TAG) == null)
+ ReviewsListDialogFragment(
+ it.title,
+ it.id.toString()
+ ).show(parentFragmentManager, MOVIE_REVIEW_DIALOG_TAG)
+ }
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupButtonActions()
+ setupGenreRecyclerView()
+ setupCastRecyclerView()
+ setupPostersAdapter()
+ configBindingClickListeners()
+ }
+
+ private fun setupCastRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ binding.castRecyclerView.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MovieDetailFragment.castAdapter
+ }
+ }
+
+ private fun setupGenreRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ binding.genreRecyclerView.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MovieDetailFragment.genreAdapter
+ }
+ }
+
+ private fun setupPostersAdapter() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ binding.posterRecyclerView.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MovieDetailFragment.postersAdapter
+ }
+ }
+
+ private fun setupButtonActions() {
+ binding.back.setOnClickListener { navController.popBackStack() }
+ }
+
+
+ private fun getMovieDetail(id: String) {
+ viewModel.getMovieDetail(id)
+ }
+
+ private fun getCredits(id: String) {
+ viewModel.getCredits(id)
+ }
+
+ private fun getReviews(id: String) {
+ viewModel.getReviews(id)
+ }
+
+ private fun getPosters(id: String) {
+ viewModel.getPosters(id)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailViewModel.kt
new file mode 100644
index 00000000..7c9c0292
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/MovieDetailViewModel.kt
@@ -0,0 +1,71 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail
+
+import androidx.lifecycle.MutableLiveData
+import com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture.IMovieDetailCoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.*
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail.IMovieDetailRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class MovieDetailViewModel
+@Inject constructor(
+ val coordinator: IMovieDetailCoordinator,
+ private val movieDetailRepository: IMovieDetailRepository,
+ override val exceptionHandler: ExceptionHandler
+) : BaseViewModel(coordinator) {
+
+ var movieDetail = MutableLiveData()
+ var credits = MutableLiveData()
+ var director = MutableLiveData("")
+ var review = MutableLiveData()
+ var reviewPreview = MutableLiveData()
+ var movieImages = MutableLiveData()
+
+ fun getMovieDetail(id: String) = launch {
+ movieDetailRepository.getMovieDetail(id)
+ .onSuccess { movieDetail.postValue(it) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getCredits(id: String) = launch {
+ movieDetailRepository.getCredits(id)
+ .onSuccess {
+ credits.postValue(it)
+ findDirector(it)
+ }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ private fun findDirector(it: Credits) {
+ it.crew.forEach() { crew ->
+ if (crew.job == "Director") director.postValue(crew.name)
+ }
+ }
+
+ fun getReviews(id: String) = launch {
+ movieDetailRepository.getReviews(id)
+ .onSuccess {
+ review.postValue(it)
+ if (!it.results.isNullOrEmpty() && it.results.size > 0) {
+ reviewPreview.postValue(it.results[0])
+ }
+ }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getPosters(id: String) = launch {
+ movieDetailRepository.getImages(id)
+ .onSuccess { movieImages.postValue(it) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/CastAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/CastAdapter.kt
new file mode 100644
index 00000000..57f30613
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/CastAdapter.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.adapter
+
+import androidx.databinding.ViewDataBinding
+import com.arashafsharpour.daresaymovie.BR
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Cast
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseSimpleListAdapter
+import javax.inject.Inject
+
+class CastAdapter @Inject() constructor() :
+ BaseSimpleListAdapter(Cast.DIFF_CALLBACK) {
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_cast
+
+ override fun getItemVariableId(): Int = BR.cast
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/GenreAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/GenreAdapter.kt
new file mode 100644
index 00000000..00ae0554
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/GenreAdapter.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.adapter
+
+import androidx.databinding.ViewDataBinding
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Genres
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseSimpleListAdapter
+import com.skydoves.bindables.BR
+import javax.inject.Inject
+
+class GenreAdapter @Inject() constructor() :
+ BaseSimpleListAdapter(Genres.DIFF_CALLBACK) {
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_genre
+
+ override fun getItemVariableId(): Int = BR.genre
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/PostersAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/PostersAdapter.kt
new file mode 100644
index 00000000..b3ab747c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/adapter/PostersAdapter.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.adapter
+
+import androidx.databinding.ViewDataBinding
+import com.arashafsharpour.daresaymovie.BR
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Posters
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseSimpleListAdapter
+import javax.inject.Inject
+
+class PostersAdapter @Inject() constructor() :
+ BaseSimpleListAdapter(Posters.DIFF_CALLBACK) {
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_poster
+
+ override fun getItemVariableId(): Int = BR.poster
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/IMovieDetailCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/IMovieDetailCoordinator.kt
new file mode 100644
index 00000000..b5834050
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/IMovieDetailCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface IMovieDetailCoordinator : ICoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailClientProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailClientProvider.kt
new file mode 100644
index 00000000..01b39e08
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailClientProvider.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture
+
+import com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail.IMovieDetailApi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class MovieDetailClientProvider {
+ @Singleton
+ @Provides
+ fun provideConfigClient(
+ retrofit: Retrofit
+ ): IMovieDetailApi {
+ return retrofit.create(IMovieDetailApi::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailCoordinator.kt
new file mode 100644
index 00000000..f716f9e3
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/moviedetail/infrastracture/MovieDetailCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class MovieDetailCoordinator @Inject constructor() : Coordinator(), IMovieDetailCoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragment.kt
new file mode 100644
index 00000000..4e5b91a1
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragment.kt
@@ -0,0 +1,180 @@
+package com.arashafsharpour.daresaymovie.features.movies
+
+import android.os.Bundle
+import android.view.View
+import androidx.core.os.bundleOf
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.FragmentMoviesBinding
+import com.arashafsharpour.daresaymovie.features.movies.adapter.MoviesListAdapter
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.EndlessRecyclerViewScrollListener
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseFragment
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MoviesFragment : BaseFragment() {
+
+ @Inject
+ lateinit var popularAdapter: MoviesListAdapter
+ @Inject
+ lateinit var upcomingAdapter: MoviesListAdapter
+ @Inject
+ lateinit var nowPlayingAdapter: MoviesListAdapter
+ @Inject
+ lateinit var topRatedAdapter: MoviesListAdapter
+ override val layoutResourceId: Int = R.layout.fragment_movies
+ override val viewModel: MoviesFragmentViewModel by viewModels()
+ private lateinit var popularEndlessScrollListener: EndlessRecyclerViewScrollListener
+ private lateinit var topRatedEndlessScrollListener: EndlessRecyclerViewScrollListener
+ private lateinit var upComingEndlessScrollListener: EndlessRecyclerViewScrollListener
+ private lateinit var nowPlayingEndlessScrollListener: EndlessRecyclerViewScrollListener
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ configMoviesRecyclerViews()
+ configBindingClickListener()
+ }
+
+ private fun configBindingClickListener() {
+ binding.profileImage.setOnClickListener {
+ navController.navigate(R.id.action_moviesFragment_to_profileFragment)
+ }
+ }
+
+ private fun configMoviesRecyclerViews() {
+ configRefreshLayout()
+ configPopularRecyclerView()
+ configTopRatedRecyclerView()
+ configNowPlayingRecyclerView()
+ configUpcomingRecyclerView()
+ }
+
+ private fun configPopularRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ popularEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getPopularMovies(MovieCategoryType.POPULAR, page) }
+ }
+ }
+ setupPopularRecyclerView(layoutManager)
+ }
+
+ private fun setupPopularRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.popularRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MoviesFragment.popularAdapter
+ addOnScrollListener(popularEndlessScrollListener)
+ }
+
+ popularAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ private fun configTopRatedRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ topRatedEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getTopRatedMovies(MovieCategoryType.TOPRATED, page) }
+ }
+ }
+ setupTopRatedRecyclerView(layoutManager)
+ }
+
+ private fun setupTopRatedRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.topRatedRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MoviesFragment.topRatedAdapter
+ addOnScrollListener(topRatedEndlessScrollListener)
+ }
+
+ topRatedAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ private fun configUpcomingRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ upComingEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getPopularMovies(MovieCategoryType.UPCOMING, page) }
+ }
+ }
+ setupUpcomingRecyclerView(layoutManager)
+ }
+
+ private fun setupUpcomingRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.upcomingRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MoviesFragment.upcomingAdapter
+ addOnScrollListener(upComingEndlessScrollListener)
+ }
+ upcomingAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ private fun configNowPlayingRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ nowPlayingEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getPopularMovies(MovieCategoryType.NOWPLAYING, page) }
+ }
+ }
+ setupNowPlayingRecyclerView(layoutManager)
+ }
+
+ private fun setupNowPlayingRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.nowPlayingRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@MoviesFragment.nowPlayingAdapter
+ addOnScrollListener(nowPlayingEndlessScrollListener)
+ }
+ nowPlayingAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ override fun initializeBindingVariables() {
+ binding.vm = viewModel
+ }
+
+ private fun configRefreshLayout() {
+ binding.swipeLayout.setOnRefreshListener {
+ binding.swipeLayout.isRefreshing = false
+ resetEndlessScrollListeners()
+ clearMoviesLists()
+ getMoviesByCategories()
+ }
+ }
+
+ private fun clearMoviesLists() {
+ viewModel.popularMovies.value = arrayListOf()
+ viewModel.topRatedMovies.value = arrayListOf()
+ viewModel.upcomingMovies.value = arrayListOf()
+ viewModel.nowPlayingMovies.value = arrayListOf()
+ }
+
+ private fun resetEndlessScrollListeners() {
+ popularEndlessScrollListener.resetState()
+ topRatedEndlessScrollListener.resetState()
+ upComingEndlessScrollListener.resetState()
+ nowPlayingEndlessScrollListener.resetState()
+ }
+
+ private fun getMoviesByCategories() {
+ viewModel.getPopularMovies(MovieCategoryType.POPULAR)
+ viewModel.getTopRatedMovies(MovieCategoryType.TOPRATED)
+ viewModel.getUpcomingMovies(MovieCategoryType.UPCOMING)
+ viewModel.getNowPlayingMovies(MovieCategoryType.NOWPLAYING)
+ }
+
+ private fun navigateToMovieDetails(id: String) {
+ val bundle = bundleOf("id" to id)
+ navController.navigate(R.id.action_moviesFragment_to_movieDetailFragment, bundle)
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragmentViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragmentViewModel.kt
new file mode 100644
index 00000000..17195a5b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/MoviesFragmentViewModel.kt
@@ -0,0 +1,99 @@
+package com.arashafsharpour.daresaymovie.features.movies
+
+import androidx.lifecycle.MutableLiveData
+import com.arashafsharpour.daresaymovie.features.movies.infrastracture.IMoviesCoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.notifyObserver
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Account
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movie
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import com.arashafsharpour.daresaymovie.persistence.API_KEY
+import com.arashafsharpour.daresaymovie.persistence.repositories.movies.IMoviesRepository
+import com.arashafsharpour.daresaymovie.persistence.repositories.profile.IProfileRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class MoviesFragmentViewModel
+@Inject constructor(
+ val coordinator: IMoviesCoordinator,
+ private val movieRepository: IMoviesRepository,
+ private val profileRepository: IProfileRepository,
+ override val exceptionHandler: ExceptionHandler
+) : BaseViewModel(coordinator) {
+
+ val popularMovies = MutableLiveData(mutableListOf())
+ val nowPlayingMovies = MutableLiveData(mutableListOf())
+ val upcomingMovies = MutableLiveData(mutableListOf())
+ val topRatedMovies = MutableLiveData(mutableListOf())
+ val profile = MutableLiveData()
+
+ fun getProfile() = launch {
+ profileRepository.getProfile(API_KEY)
+ .onSuccess { profile.postValue(it) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ init {
+ if (popularMovies.value.isNullOrEmpty()) {
+ getPopularMovies(categoryType = MovieCategoryType.POPULAR)
+ getUpcomingMovies(categoryType = MovieCategoryType.UPCOMING)
+ getNowPlayingMovies(categoryType = MovieCategoryType.NOWPLAYING)
+ getTopRatedMovies(categoryType = MovieCategoryType.TOPRATED)
+ }
+ }
+
+ fun getPopularMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ movieRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setPopularMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getTopRatedMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ movieRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setPopularMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getUpcomingMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ movieRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setPopularMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getNowPlayingMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ movieRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setPopularMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ private fun setPopularMoviesList(result: ArrayList, categoryType: MovieCategoryType) {
+ if (!result.isNullOrEmpty())
+ when (categoryType) {
+ MovieCategoryType.TOPRATED -> {
+ topRatedMovies.value?.addAll(result)
+ topRatedMovies.notifyObserver()
+ }
+ MovieCategoryType.UPCOMING -> {
+ upcomingMovies.value?.addAll(result)
+ upcomingMovies.notifyObserver()
+ }
+ MovieCategoryType.POPULAR -> {
+ popularMovies.value?.addAll(result)
+ popularMovies.notifyObserver()
+ }
+ MovieCategoryType.NOWPLAYING -> {
+ nowPlayingMovies.value?.addAll(result)
+ nowPlayingMovies.notifyObserver()
+ }
+ else -> {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/adapter/MoviesListAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/adapter/MoviesListAdapter.kt
new file mode 100644
index 00000000..1a761820
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/adapter/MoviesListAdapter.kt
@@ -0,0 +1,54 @@
+package com.arashafsharpour.daresaymovie.features.movies.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.ItemMovieBinding
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movie
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseListAdapter
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewHolder
+import javax.inject.Inject
+
+open class MoviesListAdapter @Inject constructor() :
+ BaseListAdapter(Movie.DIFF_CALLBACK) {
+
+ var onItemClicked: ((movieId: String) -> Unit)? = null
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MoviesListViewHolder = MoviesListViewHolder(
+ DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ R.layout.item_movie,
+ parent,
+ false
+ )
+ )
+
+ override fun onBindViewHolder(holder: MoviesListViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ inner class MoviesListViewHolder(
+ val binding: ItemMovieBinding
+ ) : BaseViewHolder(binding) {
+ override val lifecycleRegistry = LifecycleRegistry(this@MoviesListViewHolder)
+
+ init {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ }
+
+ fun bind(item: Movie) {
+ binding.apply {
+ lifecycleOwner = this@MoviesListViewHolder
+ movie = item
+ root.setOnClickListener { onItemClicked?.invoke(item.id.toString()) }
+ executePendingBindings()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/IMoviesCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/IMoviesCoordinator.kt
new file mode 100644
index 00000000..5f5b4641
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/IMoviesCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.movies.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface IMoviesCoordinator : ICoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesClientProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesClientProvider.kt
new file mode 100644
index 00000000..1421d8df
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesClientProvider.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.movies.infrastracture
+
+import com.arashafsharpour.daresaymovie.persistence.repositories.movies.IMoviesApi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class MoviesClientProvider {
+ @Singleton
+ @Provides
+ fun provideConfigClient(
+ retrofit: Retrofit
+ ): IMoviesApi {
+ return retrofit.create(IMoviesApi::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesCoordinator.kt
new file mode 100644
index 00000000..a9e8206c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/movies/infrastracture/MoviesCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.movies.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class MoviesCoordinator @Inject constructor() : Coordinator(), IMoviesCoordinator
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragment.kt
new file mode 100644
index 00000000..34bb1fc2
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragment.kt
@@ -0,0 +1,99 @@
+package com.arashafsharpour.daresaymovie.features.profile
+
+import android.os.Bundle
+import android.view.View
+import androidx.core.os.bundleOf
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.FragmentProfileBinding
+import com.arashafsharpour.daresaymovie.features.profile.adapter.ProfileMovieAdapter
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.EndlessRecyclerViewScrollListener
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseFragment
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ProfileFragment : BaseFragment() {
+
+ override val layoutResourceId: Int = R.layout.fragment_profile
+ override val viewModel: ProfileFragmentViewModel by viewModels()
+ @Inject
+ lateinit var favoriteMovieAdapter: ProfileMovieAdapter
+ @Inject
+ lateinit var watchLaterMovieAdapter: ProfileMovieAdapter
+ private lateinit var favoriteEndlessScrollListener: EndlessRecyclerViewScrollListener
+ private lateinit var watchLaterEndlessScrollListener: EndlessRecyclerViewScrollListener
+
+ override fun initializeBindingVariables() {
+ binding.profile = viewModel
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ configBindingClickListener()
+ configFavoriteRecyclerView()
+ configWatchlistRecyclerView()
+ }
+
+ private fun configBindingClickListener() {
+ binding.back.setOnClickListener {
+ navController.popBackStack()
+ }
+ }
+
+ private fun configFavoriteRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ favoriteEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getFavoriteMovies(MovieCategoryType.FAVORITE, page) }
+ }
+ }
+ setupFavoriteRecyclerView(layoutManager)
+ }
+
+ private fun setupFavoriteRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.favoriteRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@ProfileFragment.favoriteMovieAdapter
+ addOnScrollListener(favoriteEndlessScrollListener)
+ }
+
+ favoriteMovieAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ private fun configWatchlistRecyclerView() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.HORIZONTAL, false)
+ watchLaterEndlessScrollListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getWatchLaterMovies(MovieCategoryType.WATCHLATER, page) }
+ }
+ }
+ setupTopRatedRecyclerView(layoutManager)
+ }
+
+ private fun setupTopRatedRecyclerView(layoutManager: LinearLayoutManager) {
+ binding.watchlistRecycler.apply {
+ this.layoutManager = layoutManager
+ adapter = this@ProfileFragment.watchLaterMovieAdapter
+ addOnScrollListener(watchLaterEndlessScrollListener)
+ }
+
+ watchLaterMovieAdapter.onItemClicked = { navigateToMovieDetails(it) }
+ }
+
+ private fun navigateToMovieDetails(id: String) {
+ val bundle = bundleOf("id" to id)
+ navController.navigate(R.id.action_profileFragment_to_movieDetailFragment, bundle)
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragmentViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragmentViewModel.kt
new file mode 100644
index 00000000..df42a5f6
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/ProfileFragmentViewModel.kt
@@ -0,0 +1,62 @@
+package com.arashafsharpour.daresaymovie.features.profile
+
+import androidx.lifecycle.MutableLiveData
+import com.arashafsharpour.daresaymovie.features.profile.infrastracture.IProfileCoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.notifyObserver
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movie
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import com.arashafsharpour.daresaymovie.persistence.repositories.profile.IProfileRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class ProfileFragmentViewModel
+@Inject constructor(
+ val coordinator: IProfileCoordinator,
+ private val profileRepository: IProfileRepository,
+ override val exceptionHandler: ExceptionHandler
+ ) : BaseViewModel(coordinator) {
+
+ val favoriteMovies = MutableLiveData(mutableListOf())
+ val watchLaterMovies = MutableLiveData(mutableListOf())
+
+ init {
+ if (favoriteMovies.value.isNullOrEmpty()) {
+ getFavoriteMovies(categoryType = MovieCategoryType.FAVORITE)
+ getWatchLaterMovies(categoryType = MovieCategoryType.WATCHLATER)
+ }
+ }
+
+ fun getFavoriteMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ profileRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ fun getWatchLaterMovies(categoryType: MovieCategoryType, page: Int = 1) = launch {
+ profileRepository.getMoviesByType(categoryType, page)
+ .onSuccess { setMoviesList(it.result, categoryType) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ private fun setMoviesList(result: ArrayList, categoryType: MovieCategoryType) {
+ if (!result.isNullOrEmpty())
+ when(categoryType) {
+ MovieCategoryType.FAVORITE -> {
+ favoriteMovies.value?.addAll(result)
+ favoriteMovies.notifyObserver()
+ }
+ MovieCategoryType.WATCHLATER -> {
+ watchLaterMovies.value?.addAll(result)
+ watchLaterMovies.notifyObserver()
+ }
+ else -> {}
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/adapter/ProfileMovieAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/adapter/ProfileMovieAdapter.kt
new file mode 100644
index 00000000..cc6f505c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/adapter/ProfileMovieAdapter.kt
@@ -0,0 +1,54 @@
+package com.arashafsharpour.daresaymovie.features.profile.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.ItemMovieBinding
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movie
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseListAdapter
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewHolder
+import javax.inject.Inject
+
+class ProfileMovieAdapter @Inject constructor() :
+ BaseListAdapter(Movie.DIFF_CALLBACK) {
+
+ var onItemClicked: ((movieId: String) -> Unit)? = null
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): ProfileListViewHolder = ProfileListViewHolder(
+ DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ R.layout.item_movie,
+ parent,
+ false
+ )
+ )
+
+ override fun onBindViewHolder(holder: ProfileListViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ inner class ProfileListViewHolder(
+ val binding: ItemMovieBinding
+ ) : BaseViewHolder(binding) {
+ override val lifecycleRegistry = LifecycleRegistry(this@ProfileListViewHolder)
+
+ init {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ }
+
+ fun bind(item: Movie) {
+ binding.apply {
+ lifecycleOwner = this@ProfileListViewHolder
+ movie = item
+ root.setOnClickListener { onItemClicked?.invoke(item.id.toString()) }
+ executePendingBindings()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/IProfileCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/IProfileCoordinator.kt
new file mode 100644
index 00000000..b75efabc
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/IProfileCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.profile.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface IProfileCoordinator : ICoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileClientProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileClientProvider.kt
new file mode 100644
index 00000000..ffc63dcd
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileClientProvider.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.profile.infrastracture
+
+import com.arashafsharpour.daresaymovie.persistence.repositories.profile.IProfileApi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class ProfileClientProvider {
+ @Singleton
+ @Provides
+ fun provideProfileClient(
+ retrofit: Retrofit
+ ): IProfileApi {
+ return retrofit.create(IProfileApi::class.java)
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileCoordinator.kt
new file mode 100644
index 00000000..2b2e771c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/profile/infrastracture/ProfileCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.profile.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class ProfileCoordinator @Inject constructor() : Coordinator(), IProfileCoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogFragment.kt
new file mode 100644
index 00000000..7b8d374b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogFragment.kt
@@ -0,0 +1,82 @@
+package com.arashafsharpour.daresaymovie.features.reviews
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.BottomSheetReviewsBinding
+import com.arashafsharpour.daresaymovie.features.reviews.adapter.ReviewAdapter
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.EndlessRecyclerViewScrollListener
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseBottomSheetDialogFragment
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ReviewsListDialogFragment(private val movieName: String, private val movieId: String) :
+ BaseBottomSheetDialogFragment() {
+
+ @Inject lateinit var reviewAdapter: ReviewAdapter
+ private lateinit var reviewEndlessRecyclerViewListener: EndlessRecyclerViewScrollListener
+ override val layoutResourceId: Int = R.layout.bottom_sheet_reviews
+ override val viewModel: ReviewsListDialogViewModel by viewModels()
+
+ override fun initializeBindingVariables() {
+ binding.review = viewModel
+
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setStyle(
+ DialogFragment.STYLE_NORMAL,
+ R.style.BottomSheetDialogStyle
+ )
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewModel.movieName.postValue(movieName)
+ setupReviewRecycler()
+ setItemClickListeners()
+ configRefreshLayout()
+ viewModel.getReviews(movieId)
+ }
+
+ private fun setupReviewRecycler() {
+ val layoutManager =
+ LinearLayoutManager(requireActivity(), LinearLayoutManager.VERTICAL, false)
+ reviewEndlessRecyclerViewListener =
+ object : EndlessRecyclerViewScrollListener(layoutManager) {
+ override fun onLoadMore(page: Int) {
+ launch { viewModel.getReviews(movieId, page) }
+ }
+ }
+ setupReviewLayoutManager(layoutManager)
+ }
+
+ private fun setupReviewLayoutManager(layoutManager: LinearLayoutManager) {
+ binding.reviewRecyclerView.apply {
+ this.layoutManager = layoutManager
+ adapter = this@ReviewsListDialogFragment.reviewAdapter
+ addOnScrollListener(reviewEndlessRecyclerViewListener)
+ }
+ }
+
+ private fun configRefreshLayout() {
+ binding.swipeLayout.setOnRefreshListener {
+ binding.swipeLayout.isRefreshing = false
+ reviewEndlessRecyclerViewListener.resetState()
+ viewModel.review.value?.clear()
+ viewModel.getReviews(movieId)
+ }
+ }
+
+ private fun setItemClickListeners() {
+ binding.back.setOnClickListener {
+ dismiss()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogViewModel.kt
new file mode 100644
index 00000000..73f9fb73
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/ReviewsListDialogViewModel.kt
@@ -0,0 +1,40 @@
+package com.arashafsharpour.daresaymovie.features.reviews
+
+import androidx.lifecycle.MutableLiveData
+import com.arashafsharpour.daresaymovie.features.reviews.infrastracture.IReviewsListCoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.notifyObserver
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.ReviewResults
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail.IMovieDetailRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import java.util.ArrayList
+import javax.inject.Inject
+
+@HiltViewModel
+class ReviewsListDialogViewModel
+@Inject constructor(
+ val coordinator: IReviewsListCoordinator,
+ private val movieDetailRepository: IMovieDetailRepository,
+ override val exceptionHandler: ExceptionHandler
+) :
+ BaseViewModel(coordinator) {
+
+ var review = MutableLiveData(mutableListOf())
+ var movieName = MutableLiveData()
+
+ fun getReviews(movieId: String, page: Int = 1) = launch {
+ movieDetailRepository.getReviews(movieId, page)
+ .onSuccess { setReviewValues(it.results) }
+ .onError { exceptionHandler.handle(it, coordinator) }
+ .call()
+ }
+
+ private fun setReviewValues(it: ArrayList) {
+ if (!it.isNullOrEmpty()) {
+ review.value?.addAll(it)
+ review.notifyObserver()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/adapter/ReviewAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/adapter/ReviewAdapter.kt
new file mode 100644
index 00000000..44673186
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/adapter/ReviewAdapter.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.features.reviews.adapter
+
+import androidx.databinding.ViewDataBinding
+import com.arashafsharpour.daresaymovie.BR
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.ReviewResults
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseSimpleListAdapter
+import javax.inject.Inject
+
+class ReviewAdapter @Inject() constructor() :
+ BaseSimpleListAdapter(ReviewResults.DIFF_CALLBACK) {
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_review
+
+ override fun getItemVariableId(): Int = BR.review
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/IReviewsListCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/IReviewsListCoordinator.kt
new file mode 100644
index 00000000..303d3538
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/IReviewsListCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.reviews.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface IReviewsListCoordinator : ICoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/ReviewsListCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/ReviewsListCoordinator.kt
new file mode 100644
index 00000000..9d5171cd
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/reviews/infrastracture/ReviewsListCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.reviews.infrastracture
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class ReviewsListCoordinator @Inject constructor() : Coordinator(), IReviewsListCoordinator
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashActivity.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashActivity.kt
new file mode 100644
index 00000000..686d00c9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashActivity.kt
@@ -0,0 +1,44 @@
+package com.arashafsharpour.daresaymovie.features.splash
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.databinding.ActivitySplashBinding
+import com.arashafsharpour.daresaymovie.features.main.MainActivity
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseActivity
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SplashActivity : BaseActivity() {
+
+ override val viewModel: SplashViewModel by viewModels()
+ override val layoutRes: Int = R.layout.activity_splash
+ override val navigationId: Int = 0
+ override val hasNavigation: Boolean = false
+
+ override fun initializeBindingVariables() {
+ binding.vm = viewModel
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ observeToViewModel()
+ viewModel.getConfig()
+ }
+
+ private fun observeToViewModel() {
+ viewModel.isConfigRecieved.observe(this) {
+ if (it) relocateToMainActivity()
+ }
+ }
+
+ private fun relocateToMainActivity() {
+ Intent().apply {
+ intent.setClass(this@SplashActivity, MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_NEW_TASK
+ startActivity(intent)
+ finish()
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashViewModel.kt
new file mode 100644
index 00000000..f1f69afc
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/SplashViewModel.kt
@@ -0,0 +1,40 @@
+package com.arashafsharpour.daresaymovie.features.splash
+
+import androidx.lifecycle.MutableLiveData
+import com.arashafsharpour.daresaymovie.features.splash.infrastructure.ISplashCoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.launch
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Config
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+import com.arashafsharpour.daresaymovie.persistence.repositories.config.IConfigRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class SplashViewModel
+@Inject constructor(
+ val coordinator: ISplashCoordinator,
+ private val configRepository: IConfigRepository,
+ override val exceptionHandler: ExceptionHandler
+) : BaseViewModel(coordinator) {
+
+ private val isLoading = MutableLiveData(false)
+ var isConfigRecieved = MutableLiveData(false)
+
+ fun getConfig() = launch {
+ configRepository.getConfig()
+ .beforeCall { isLoading.postValue(true) }
+ .afterCall { isLoading.postValue(false) }
+ .onSuccess { saveConfigLocally(it) }
+ .onError {
+ exceptionHandler.handle(it, coordinator)
+ isConfigRecieved.postValue(true)
+ }
+ .call()
+ }
+
+ private fun saveConfigLocally(config: Config) {
+ configRepository.saveConfigLocally(config).apply { isConfigRecieved.postValue(this) }
+ }
+}
+
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/ISplashCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/ISplashCoordinator.kt
new file mode 100644
index 00000000..2ac60193
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/ISplashCoordinator.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.features.splash.infrastructure
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface ISplashCoordinator : ICoordinator
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashClientProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashClientProvider.kt
new file mode 100644
index 00000000..7a485093
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashClientProvider.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.features.splash.infrastructure
+
+import com.arashafsharpour.daresaymovie.persistence.repositories.config.IConfigApi
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class SplashClientProvider {
+ @Singleton
+ @Provides
+ fun provideConfigClient(
+ retrofit: Retrofit
+ ): IConfigApi {
+ return retrofit.create(IConfigApi::class.java)
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashCoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashCoordinator.kt
new file mode 100644
index 00000000..033dd562
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/features/splash/infrastructure/SplashCoordinator.kt
@@ -0,0 +1,6 @@
+package com.arashafsharpour.daresaymovie.features.splash.infrastructure
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.Coordinator
+import javax.inject.Inject
+
+class SplashCoordinator @Inject constructor() : Coordinator(), ISplashCoordinator
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ActionCommand.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ActionCommand.kt
new file mode 100644
index 00000000..b4277630
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ActionCommand.kt
@@ -0,0 +1,51 @@
+package com.arashafsharpour.daresaymovie.infrastructure.coordinator
+
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LifecycleOwner
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.showSnackBar
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.showToast
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseViewModel
+
+sealed class ActionCommand {
+ data class ShowToast(val message: String, val duration: Int) : ActionCommand()
+ data class ShowToastByResource(@StringRes val message: Int, val duration: Int) : ActionCommand()
+ data class ShowSnackBar(val message: String, val duration: Int) : ActionCommand()
+ data class ShowSnackBarByResource(@StringRes val message: Int, val duration: Int) :
+ ActionCommand()
+}
+
+fun LifecycleOwner.observeActions(
+ viewModel: BaseViewModel
+) {
+ viewModel.getPrimitiveCoordinator().actionCommand.observeEvent(this) { command ->
+ when (command) {
+ is ActionCommand.ShowToast -> when (this) {
+ is ComponentActivity -> showToast(command.message, command.duration)
+ is Fragment -> showToast(command.message, command.duration)
+ }
+ is ActionCommand.ShowToastByResource -> when (this) {
+ is ComponentActivity -> showToast(command.message, command.duration)
+ is Fragment -> showToast(command.message, command.duration)
+ }
+ is ActionCommand.ShowSnackBar -> when (this) {
+ is Fragment -> showSnackBar(command.message, command.duration)
+ is ComponentActivity -> {
+ Toast.makeText(this, command.message, Toast.LENGTH_SHORT).show()
+ }
+ }
+ is ActionCommand.ShowSnackBarByResource -> when (this) {
+ is Fragment ->
+ showSnackBar(command.message, command.duration)
+ is ComponentActivity ->
+ findViewById(android.R.id.content).showSnackBar(
+ command.message,
+ command.duration
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Coordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Coordinator.kt
new file mode 100644
index 00000000..3fc1a740
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Coordinator.kt
@@ -0,0 +1,46 @@
+package com.arashafsharpour.daresaymovie.infrastructure.coordinator
+
+import androidx.annotation.StringRes
+import androidx.lifecycle.MutableLiveData
+
+abstract class Coordinator: ICoordinator {
+
+ override val actionCommand = MutableLiveData>()
+
+ override fun showError(message: String) {
+ showSnackBar(message)
+ }
+
+ override fun showError(messageResourceId: Int) {
+ showSnackBar(messageResourceId)
+ }
+
+ override fun showToast(message: String, duration: Int) {
+ this.actionCommand.postValue(Event(ActionCommand.ShowToast(message, duration)))
+ }
+
+ override fun showToast(messageResourceId: Int, duration: Int) {
+ this.actionCommand.postValue(
+ Event(
+ ActionCommand.ShowToastByResource(
+ messageResourceId,
+ duration
+ )
+ )
+ )
+ }
+
+ override fun showSnackBar(message: String, duration: Int) {
+ this.actionCommand.postValue(Event(ActionCommand.ShowSnackBar(message, duration)))
+ }
+
+ override fun showSnackBar(@StringRes message: Int, duration: Int) {
+ this.actionCommand.postValue(Event(ActionCommand.ShowSnackBarByResource(message, duration)))
+ }
+
+ override fun tryToShowMessage(message: String?) {
+ if (!message.isNullOrBlank()) {
+ showSnackBar(message)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Event.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Event.kt
new file mode 100644
index 00000000..2f220407
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/Event.kt
@@ -0,0 +1,54 @@
+package com.arashafsharpour.daresaymovie.infrastructure.coordinator
+
+import androidx.annotation.MainThread
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+
+open class Event(private val content: T) {
+
+ private var consumed = false
+
+ fun consume(): T? {
+ return if (consumed) {
+ null
+ } else {
+ consumed = true
+ content
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Event<*>
+
+ if (content != other.content) return false
+ if (consumed != other.consumed) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = content?.hashCode() ?: 0
+ result = 31 * result + consumed.hashCode()
+ return result
+ }
+}
+
+class EventObserver(private val onEventUnconsumedContent: (T) -> Unit) : Observer> {
+ override fun onChanged(event: Event?) {
+ event?.consume()?.run(onEventUnconsumedContent)
+ }
+}
+
+@MainThread
+inline fun LiveData>.observeEvent(
+ owner: LifecycleOwner,
+ crossinline onChanged: (T) -> Unit
+): EventObserver {
+ return EventObserver { t -> onChanged.invoke(t) }.apply {
+ observe(owner, this)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ICoordinator.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ICoordinator.kt
new file mode 100644
index 00000000..072eadaf
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/coordinator/ICoordinator.kt
@@ -0,0 +1,17 @@
+package com.arashafsharpour.daresaymovie.infrastructure.coordinator
+
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.lifecycle.MutableLiveData
+import com.google.android.material.snackbar.Snackbar
+
+interface ICoordinator {
+ val actionCommand: MutableLiveData>
+ fun showError(message: String)
+ fun showError(@StringRes messageResourceId: Int)
+ fun showToast(message: String, duration: Int = Toast.LENGTH_SHORT)
+ fun showToast(@StringRes messageResourceId: Int, duration: Int = Toast.LENGTH_SHORT)
+ fun showSnackBar(message: String, duration: Int = Snackbar.LENGTH_SHORT)
+ fun showSnackBar(@StringRes message: Int, duration: Int = Snackbar.LENGTH_SHORT)
+ fun tryToShowMessage(message: String?)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/AppModule.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/AppModule.kt
new file mode 100644
index 00000000..6e129ceb
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/AppModule.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import android.app.Application
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class AppModule {
+
+ @Singleton
+ @Provides
+ fun provideContext(application: Application): Context = application.applicationContext
+
+ @Singleton
+ @Provides
+ fun providesCoroutineScope(): CoroutineScope =
+ CoroutineScope(SupervisorJob() + Dispatchers.Default)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MainModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MainModuleBinder.kt
new file mode 100644
index 00000000..e65d47c5
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MainModuleBinder.kt
@@ -0,0 +1,21 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.main.IMainCoordinator
+import com.arashafsharpour.daresaymovie.features.main.MainCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface MainModuleBinder {
+
+ @Binds
+ fun bindMainCoordinator(
+ mainCoordinator: MainCoordinator
+ ): IMainCoordinator
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MovieDetailModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MovieDetailModuleBinder.kt
new file mode 100644
index 00000000..1d96e69b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MovieDetailModuleBinder.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture.IMovieDetailCoordinator
+import com.arashafsharpour.daresaymovie.features.moviedetail.infrastracture.MovieDetailCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface MovieDetailModuleBinder {
+ @Binds
+ fun bindMainCoordinator(
+ splashCoordinator: MovieDetailCoordinator
+ ): IMovieDetailCoordinator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MoviesModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MoviesModuleBinder.kt
new file mode 100644
index 00000000..3c741c32
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/MoviesModuleBinder.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.movies.infrastracture.IMoviesCoordinator
+import com.arashafsharpour.daresaymovie.features.movies.infrastracture.MoviesCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface MoviesModuleBinder {
+ @Binds
+ fun bindMainCoordinator(
+ splashCoordinator: MoviesCoordinator
+ ): IMoviesCoordinator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/NetworkModule.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/NetworkModule.kt
new file mode 100644
index 00000000..6b24ff07
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/NetworkModule.kt
@@ -0,0 +1,73 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiCallHeaderInterceptor
+import com.arashafsharpour.daresaymovie.persistence.TIMEOUT
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.MapperFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.registerKotlinModule
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Converter
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.converter.jackson.JacksonConverterFactory
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Singleton
+
+const val DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ"
+
+@Module
+@InstallIn(SingletonComponent::class)
+class NetworkModule {
+
+ @Provides
+ @Singleton
+ fun provideRetrofit(): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl("https:/api.themoviedb.org/")
+ .client(createClient())
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ private fun createClient(): OkHttpClient {
+ val okHttpClientBuilder: OkHttpClient.Builder = OkHttpClient.Builder().apply {
+ connectTimeout(TIMEOUT, TimeUnit.SECONDS)
+ readTimeout(TIMEOUT, TimeUnit.SECONDS)
+ writeTimeout(TIMEOUT, TimeUnit.SECONDS)
+ }
+ .addInterceptor(ApiCallHeaderInterceptor())
+ val loggingInterceptor =
+ HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
+ okHttpClientBuilder.addInterceptor(loggingInterceptor)
+ return okHttpClientBuilder.build()
+ }
+
+ @Singleton
+ @Provides
+ fun provideJsonConvertorFactory(objectMapper: ObjectMapper): Converter.Factory {
+ return JacksonConverterFactory.create(objectMapper)
+ }
+
+ @Singleton
+ @Provides
+ fun provideJackSonObjectMapper(): ObjectMapper {
+ val objectMapper = ObjectMapper().registerKotlinModule()
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ objectMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
+ objectMapper.dateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.ENGLISH)
+ objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
+
+ return objectMapper
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ProfileModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ProfileModuleBinder.kt
new file mode 100644
index 00000000..36705b23
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ProfileModuleBinder.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.profile.infrastracture.IProfileCoordinator
+import com.arashafsharpour.daresaymovie.features.profile.infrastracture.ProfileCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface ProfileModuleBinder {
+ @Binds
+ fun bindMainCoordinator(
+ reviewsCoordinator: ProfileCoordinator
+ ): IProfileCoordinator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ReviewsModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ReviewsModuleBinder.kt
new file mode 100644
index 00000000..519d08fa
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/ReviewsModuleBinder.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.reviews.infrastracture.IReviewsListCoordinator
+import com.arashafsharpour.daresaymovie.features.reviews.infrastracture.ReviewsListCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface ReviewsModuleBinder {
+ @Binds
+ fun bindMainCoordinator(
+ reviewsCoordinator: ReviewsListCoordinator
+ ): IReviewsListCoordinator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/SplashModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/SplashModuleBinder.kt
new file mode 100644
index 00000000..42ef5c20
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/di/SplashModuleBinder.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.di
+
+import com.arashafsharpour.daresaymovie.features.splash.infrastructure.ISplashCoordinator
+import com.arashafsharpour.daresaymovie.features.splash.infrastructure.SplashCoordinator
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.FlowPreview
+
+@FlowPreview
+@InstallIn(SingletonComponent::class)
+@Module
+interface SplashModuleBinder {
+ @Binds
+ fun bindMainCoordinator(
+ splashCoordinator: SplashCoordinator
+ ): ISplashCoordinator
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandler.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandler.kt
new file mode 100644
index 00000000..ecb0e95c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandler.kt
@@ -0,0 +1,37 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy.GeneralExceptionPolicy
+import timber.log.Timber
+import java.util.concurrent.CancellationException
+import javax.inject.Inject
+
+class ExceptionHandler @Inject constructor(
+ private val policies: Set<@JvmSuppressWildcards ExceptionPolicy>
+) {
+
+ fun handle(
+ throwable: Throwable,
+ coordinator: ICoordinator,
+ onExceptionListener: ((exception: Throwable, policy: ExceptionPolicy) -> Boolean)? = null
+ ) {
+ val eligiblePolicies = policies.filter { it.isEligible(throwable) }
+ when {
+ eligiblePolicies.any() -> {
+ eligiblePolicies.forEach {
+ val shouldRunOriginalPolicy = onExceptionListener?.invoke(throwable, it) ?: true
+ if (shouldRunOriginalPolicy) {
+ it.handle(throwable, coordinator)
+ return
+ }
+ }
+ }
+ throwable is CancellationException -> {
+ Timber.e(throwable)
+ }
+ else -> {
+ GeneralExceptionPolicy().handle(throwable, coordinator)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandlerModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandlerModuleBinder.kt
new file mode 100644
index 00000000..b72e1111
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionHandlerModuleBinder.kt
@@ -0,0 +1,51 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling
+
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy.*
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import dagger.multibindings.IntoSet
+
+@InstallIn(SingletonComponent::class)
+@Module
+interface ExceptionHandlerModuleBinder {
+
+ @Binds
+ @IntoSet
+ fun bindBadRequestExceptionPolicy(
+ badRequestExceptionPolicy: BadRequestExceptionPolicy
+ ): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindResourceNotFoundExceptionPolicy(
+ resourceNotFoundExceptionPolicy: ResourceNotFoundExceptionPolicy
+ ): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindServerExceptionPolicy(serverExceptionPolicy: ServerExceptionPolicy): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindNetworkTimeoutExceptionPolicy(
+ networkTimeoutExceptionPolicy: NetworkTimeoutExceptionPolicy
+ ): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindGeneralExceptionPolicy(generalExceptionPolicy: GeneralExceptionPolicy): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindUnProcessableExceptionPolicy(
+ unProcessableExceptionPolicy: UnProcessableExceptionPolicy
+ ): ExceptionPolicy
+
+ @Binds
+ @IntoSet
+ fun bindForbiddenAccessPolicy(
+ forbiddenAccess: ForbiddenAccess
+ ): ExceptionPolicy
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicy.kt
new file mode 100644
index 00000000..18581159
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicy.kt
@@ -0,0 +1,8 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling
+
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+
+interface ExceptionPolicy {
+ fun isEligible(exception: Throwable): Boolean
+ fun handle(exception: Throwable, coordinator: ICoordinator)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicyKey.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicyKey.kt
new file mode 100644
index 00000000..76bcc6cb
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/ExceptionPolicyKey.kt
@@ -0,0 +1,14 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling
+
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER
+)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+annotation class ExceptionPolicyKey(val value: KClass)
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/HttpExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/HttpExceptionPolicy.kt
new file mode 100644
index 00000000..65d3824b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/HttpExceptionPolicy.kt
@@ -0,0 +1,42 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling
+
+import android.content.Context
+import android.util.Log
+import androidx.annotation.CallSuper
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.fasterxml.jackson.databind.ObjectMapper
+import retrofit2.HttpException
+import timber.log.Timber
+import javax.inject.Inject
+
+abstract class HttpExceptionPolicy : ExceptionPolicy {
+
+ @Inject
+ lateinit var objectMapper: ObjectMapper
+
+ @Inject
+ lateinit var context: Context
+
+ var errorBody: String? = null
+ override fun isEligible(exception: Throwable): Boolean {
+ return exception is HttpException && exception.code() == getHttpCode()
+ }
+
+ abstract fun getHttpCode(): Int
+
+ @CallSuper
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ errorBody = parseError(exception as HttpException)
+ }
+
+ private fun parseError(e: HttpException): String? {
+ if (e.response() != null && e.response()!!.errorBody() != null) {
+ try {
+ return e.response()!!.errorBody()!!.string()
+ } catch (e: java.lang.Exception) {
+ Timber.e(e)
+ }
+ }
+ return null
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/InvalidResponseException.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/InvalidResponseException.kt
new file mode 100644
index 00000000..5409d589
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/InvalidResponseException.kt
@@ -0,0 +1,3 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.exceptions
+
+class InvalidResponseException(property: String) : Exception(property)
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/NetworkConnectionException.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/NetworkConnectionException.kt
new file mode 100644
index 00000000..40ee07a2
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/NetworkConnectionException.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.exceptions
+
+import java.io.IOException
+
+class NetworkConnectionException : IOException("Network is not Available")
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/UnAuthorizedException.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/UnAuthorizedException.kt
new file mode 100644
index 00000000..d1a360d3
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/exceptions/UnAuthorizedException.kt
@@ -0,0 +1,3 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.exceptions
+
+class UnAuthorizedException : RuntimeException()
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/BadRequestExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/BadRequestExceptionPolicy.kt
new file mode 100644
index 00000000..a4fc8c29
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/BadRequestExceptionPolicy.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import javax.inject.Inject
+
+class BadRequestExceptionPolicy @Inject constructor() :
+ HttpExceptionPolicy() {
+ override fun getHttpCode(): Int {
+ return 400
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ super.handle(exception, coordinator)
+ coordinator.showSnackBar(
+ context.getString(R.string.error_badRequest)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ForbiddenAccess.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ForbiddenAccess.kt
new file mode 100644
index 00000000..ebdd4f35
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ForbiddenAccess.kt
@@ -0,0 +1,18 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import javax.inject.Inject
+
+class ForbiddenAccess @Inject constructor() : HttpExceptionPolicy() {
+ override fun getHttpCode(): Int {
+ return 403
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ super.handle(exception, coordinator)
+ coordinator.showSnackBar(context.getString(R.string.error_server)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/GeneralExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/GeneralExceptionPolicy.kt
new file mode 100644
index 00000000..63606136
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/GeneralExceptionPolicy.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionPolicy
+import javax.inject.Inject
+
+class GeneralExceptionPolicy @Inject constructor() : ExceptionPolicy {
+ override fun isEligible(exception: Throwable): Boolean {
+ return false
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ coordinator.showSnackBar(R.string.error_server)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/NetworkTimeoutExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/NetworkTimeoutExceptionPolicy.kt
new file mode 100644
index 00000000..f86e259e
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/NetworkTimeoutExceptionPolicy.kt
@@ -0,0 +1,30 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.exceptions.NetworkConnectionException
+import retrofit2.HttpException
+import java.net.SocketException
+import java.net.SocketTimeoutException
+import javax.inject.Inject
+
+class NetworkTimeoutExceptionPolicy @Inject constructor() : HttpExceptionPolicy() {
+
+ override fun getHttpCode(): Int {
+ return 40
+ }
+
+ override fun isEligible(exception: Throwable): Boolean {
+ return exception is SocketTimeoutException ||
+ exception is NetworkConnectionException ||
+ exception is SocketException ||
+ exception is HttpException
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ super.handle(exception, coordinator)
+ coordinator.showSnackBar(context.getString(R.string.error_server)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ResourceNotFoundExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ResourceNotFoundExceptionPolicy.kt
new file mode 100644
index 00000000..0fdc97c1
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ResourceNotFoundExceptionPolicy.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import javax.inject.Inject
+
+class ResourceNotFoundExceptionPolicy @Inject constructor() : HttpExceptionPolicy() {
+ override fun getHttpCode(): Int {
+ return 404
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ServerExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ServerExceptionPolicy.kt
new file mode 100644
index 00000000..e724a653
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/ServerExceptionPolicy.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import javax.inject.Inject
+
+class ServerExceptionPolicy @Inject constructor() :
+ HttpExceptionPolicy() {
+ override fun getHttpCode(): Int {
+ return 500
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ super.handle(exception, coordinator)
+ coordinator.showSnackBar(
+ context.getString(R.string.error_server)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/UnProcessableExceptionPolicy.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/UnProcessableExceptionPolicy.kt
new file mode 100644
index 00000000..8c46e991
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/exceptions/exceptionHandling/policy/UnProcessableExceptionPolicy.kt
@@ -0,0 +1,18 @@
+package com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy
+
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.HttpExceptionPolicy
+import javax.inject.Inject
+
+class UnProcessableExceptionPolicy @Inject constructor() : HttpExceptionPolicy() {
+ override fun getHttpCode(): Int {
+ return 422
+ }
+
+ override fun handle(exception: Throwable, coordinator: ICoordinator) {
+ super.handle(exception, coordinator)
+ coordinator.showSnackBar(context.getString(R.string.error_server)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/BaseItemDecoration.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/BaseItemDecoration.kt
new file mode 100755
index 00000000..07c71da9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/BaseItemDecoration.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration
+
+abstract class BaseItemDecoration : ItemDecoration() {
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State
+ ) {
+ val position = parent.getChildViewHolder(view).bindingAdapterPosition
+ val itemCount = state.itemCount
+ val layoutManager = parent.layoutManager
+ setSpacingForDirection(outRect, layoutManager, position, itemCount)
+ }
+
+ abstract fun setSpacingForDirection(
+ outRect: Rect,
+ layoutManager: RecyclerView.LayoutManager?,
+ position: Int,
+ itemCount: Int
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Context.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Context.kt
new file mode 100644
index 00000000..20d63ca3
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Context.kt
@@ -0,0 +1,28 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.net.ConnectivityManager
+import android.widget.Toast
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+
+val Context.connectivityManager: ConnectivityManager
+ get() =
+ this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+
+fun Context.getColorFromResource(@ColorRes colorResId: Int): Int {
+ return ContextCompat.getColor(this, colorResId)
+}
+
+fun Context.getDrawableFromResource(@DrawableRes resId: Int): Drawable? {
+ return ContextCompat.getDrawable(this, resId)
+}
+
+fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
+ Toast.makeText(this, message, duration).show()
+
+fun Context.showToast(@StringRes message: Int, duration: Int = Toast.LENGTH_SHORT) =
+ Toast.makeText(this, message, duration).show()
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Coroutines.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Coroutines.kt
new file mode 100644
index 00000000..5f0914c0
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Coroutines.kt
@@ -0,0 +1,47 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+fun CoroutineScope.launchIdling(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+): Job {
+ GlobalIdlingResource.increment()
+ val job = this.launch(context, start, block)
+ job.invokeOnCompletion { GlobalIdlingResource.decrement() }
+ return job
+}
+
+fun ViewModel.launch(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+) = viewModelScope.launch(context, start, block)
+
+fun LifecycleOwner.launch(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+) = lifecycleScope.launch(context, start, block)
+
+fun LifecycleOwner.launchWhenCreated(
+ block: suspend CoroutineScope.() -> Unit
+) = lifecycleScope.launchWhenCreated(block)
+
+fun LifecycleOwner.launchWhenStarted(
+ block: suspend CoroutineScope.() -> Unit
+) = lifecycleScope.launchWhenStarted(block)
+
+fun LifecycleOwner.launchWhenResumed(
+ block: suspend CoroutineScope.() -> Unit
+) = lifecycleScope.launchWhenResumed(block)
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/CurrentFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/CurrentFragment.kt
new file mode 100644
index 00000000..6c1f6cd9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/CurrentFragment.kt
@@ -0,0 +1,8 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+object CurrentFragment {
+ lateinit var currentClass: Class<*>
+ var data: Map? = null
+
+ fun isInitialized() = this::currentClass.isInitialized
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/EndlessRecyclerViewScrollListener.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/EndlessRecyclerViewScrollListener.kt
new file mode 100644
index 00000000..95676636
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/EndlessRecyclerViewScrollListener.kt
@@ -0,0 +1,118 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.os.Handler
+import android.os.Looper
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.StaggeredGridLayoutManager
+
+private const val STARTING_PAGE_INDEX = 1
+
+abstract class EndlessRecyclerViewScrollListener(
+ layoutManager: RecyclerView.LayoutManager,
+ private val initialPage: Int = STARTING_PAGE_INDEX,
+ visibleThreshold: Int = 10
+) : RecyclerView.OnScrollListener() {
+
+ private var visibleThreshold = 10
+
+ private var currentPage = initialPage
+
+ private var previousTotalItemCount = 0
+
+ private var loading = true
+
+ private var alreadyDecreasedPageForFailure = false
+
+ abstract fun onLoadMore(page: Int)
+
+ private var mLayoutManager: RecyclerView.LayoutManager = layoutManager
+
+ init {
+ when (layoutManager) {
+ is LinearLayoutManager ->
+ this.visibleThreshold = visibleThreshold
+ is GridLayoutManager ->
+ this.visibleThreshold = visibleThreshold * layoutManager.spanCount
+ is StaggeredGridLayoutManager ->
+ this.visibleThreshold = visibleThreshold * layoutManager.spanCount
+ }
+ }
+
+ private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
+ var maxSize = 0
+ for (i in lastVisibleItemPositions.indices) {
+ if (i == 0) {
+ maxSize = lastVisibleItemPositions[i]
+ } else if (lastVisibleItemPositions[i] > maxSize) {
+ maxSize = lastVisibleItemPositions[i]
+ }
+ }
+ return maxSize
+ }
+
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+ var lastVisibleItemPosition = 0
+ val totalItemCount = mLayoutManager.itemCount
+
+ when (mLayoutManager) {
+ is StaggeredGridLayoutManager -> {
+ val lastVisibleItemPositions =
+ (mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(
+ null
+ )
+ lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
+ }
+ is GridLayoutManager ->
+ lastVisibleItemPosition =
+ (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition()
+ is LinearLayoutManager ->
+ lastVisibleItemPosition =
+ (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
+ }
+
+ if (loading && totalItemCount > previousTotalItemCount) {
+ loading = false
+ previousTotalItemCount = totalItemCount
+ }
+
+ if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
+ onLoadMore(++currentPage)
+ alreadyDecreasedPageForFailure = false
+ loading = true
+ }
+ }
+
+ fun resetState() {
+ currentPage = initialPage
+ previousTotalItemCount = 0
+ loading = true
+ scrollToTop()
+ }
+
+ fun failed() {
+ Handler(Looper.getMainLooper()).postDelayed({
+ loading = false
+ }, 1000)
+ if (!alreadyDecreasedPageForFailure) {
+ currentPage--
+ alreadyDecreasedPageForFailure = true
+ }
+ }
+
+ fun setCurrentPageNumber(pageNumber: Int) {
+ this.currentPage = pageNumber
+ }
+
+ private fun scrollToTop() {
+ when (mLayoutManager) {
+ is LinearLayoutManager ->
+ (mLayoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0)
+ is GridLayoutManager ->
+ (mLayoutManager as GridLayoutManager).scrollToPositionWithOffset(0, 0)
+ is StaggeredGridLayoutManager ->
+ (mLayoutManager as StaggeredGridLayoutManager).scrollToPositionWithOffset(0, 0)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Fragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Fragment.kt
new file mode 100644
index 00000000..06520b1f
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Fragment.kt
@@ -0,0 +1,23 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.widget.Toast
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import com.google.android.material.snackbar.Snackbar
+
+
+fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
+ context?.showToast(message, duration)
+
+fun Fragment.showToast(@StringRes message: Int, duration: Int = Toast.LENGTH_SHORT) =
+ context?.showToast(message, duration)
+
+fun Fragment.showSnackBar(
+ @StringRes message: Int,
+ duration: Int = Snackbar.LENGTH_SHORT
+) = view?.showSnackBar(message, duration)
+
+fun Fragment.showSnackBar(
+ message: String,
+ duration: Int = Snackbar.LENGTH_SHORT
+) = view?.showSnackBar(message, duration)
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/GlobalIdlingResource.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/GlobalIdlingResource.kt
new file mode 100644
index 00000000..d3653e02
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/GlobalIdlingResource.kt
@@ -0,0 +1,14 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.test.espresso.idling.CountingIdlingResource
+
+
+object GlobalIdlingResource {
+
+ private const val resource = "GLOBAL"
+ private val countingIdlingResource = CountingIdlingResource(resource, true)
+
+ fun increment() = countingIdlingResource.increment()
+
+ fun decrement() = countingIdlingResource.decrement()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/HorizontalItemDecoration.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/HorizontalItemDecoration.kt
new file mode 100755
index 00000000..99e2f8a5
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/HorizontalItemDecoration.kt
@@ -0,0 +1,20 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.graphics.Rect
+import androidx.recyclerview.widget.RecyclerView
+
+class HorizontalItemDecoration constructor(
+ private val spacing: Int,
+ private val horizontalPadding: Int = 0
+) :
+ BaseItemDecoration() {
+ override fun setSpacingForDirection(
+ outRect: Rect,
+ layoutManager: RecyclerView.LayoutManager?,
+ position: Int,
+ itemCount: Int
+ ) {
+ outRect.right = if (position == 0) horizontalPadding else spacing
+ outRect.left = if (position == itemCount - 1) horizontalPadding else 0
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Image.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Image.kt
new file mode 100644
index 00000000..ea1c8f71
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Image.kt
@@ -0,0 +1,35 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.databinding.BindingAdapter
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.persistence.BASE_IMAGE_URL
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.bumptech.glide.request.transition.DrawableCrossFadeFactory
+import de.hdodenhof.circleimageview.CircleImageView
+
+
+@BindingAdapter("imageUrl")
+fun AppCompatImageView.loadImage(url: String?) {
+ if (!url.isNullOrEmpty()) {
+ val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build()
+ Glide.with(this.context).load("$BASE_IMAGE_URL/original$url")
+ .error(context.getDrawable(R.mipmap.avatar_place_holder))
+ .transition(DrawableTransitionOptions.withCrossFade(factory))
+ .diskCacheStrategy(DiskCacheStrategy.ALL).into(this)
+ }
+}
+
+@BindingAdapter("loadAvatar")
+fun CircleImageView.loadAvatar(url: String?) {
+ if (!url.isNullOrEmpty()) {
+ val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build()
+ Glide.with(this.context)
+ .load("$BASE_IMAGE_URL/original$url")
+ .error(context.getDrawable(R.mipmap.avatar_place_holder))
+ .transition(DrawableTransitionOptions.withCrossFade(factory))
+ .diskCacheStrategy(DiskCacheStrategy.ALL).into(this)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/LiveData.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/LiveData.kt
new file mode 100644
index 00000000..de234d2b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/LiveData.kt
@@ -0,0 +1,7 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.lifecycle.MutableLiveData
+
+fun MutableLiveData.notifyObserver() {
+ this.value = value
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Network.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Network.kt
new file mode 100644
index 00000000..56bc77d8
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Network.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
+
+val Context.isConnected: Boolean
+ get() {
+ val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
+ val nw = connectivityManager.activeNetwork ?: return false
+ val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
+ when {
+ actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
+ actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
+ else -> false
+ }
+ }
+ else -> {
+ // Use depreciated methods only on older devices
+ val nwInfo = connectivityManager.activeNetworkInfo ?: return false
+ nwInfo.isConnected
+ }
+ }
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/RecyclerView.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/RecyclerView.kt
new file mode 100644
index 00000000..8d8955d0
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/RecyclerView.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.annotation.DimenRes
+import androidx.databinding.BindingAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.arashafsharpour.daresaymovie.infrastructure.platform.BaseListAdapter
+
+@Suppress("UNCHECKED_CAST")
+@BindingAdapter("data")
+fun RecyclerView.setRecyclerViewData(data: List?) {
+ val list = if (data is MutableList) data else data?.toMutableList()
+ if (adapter is BaseListAdapter<*, *>) {
+ (adapter as BaseListAdapter).submitList(list)
+ }
+}
+
+fun RecyclerView.addHorizontalItemDecoration(
+ @DimenRes spacing: Int,
+ @DimenRes horizontalPadding: Int
+) {
+ addItemDecoration(
+ HorizontalItemDecoration(
+ getDimensionPixelSize(spacing),
+ getDimensionPixelSize(horizontalPadding)
+ )
+ )
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Snackbar.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Snackbar.kt
new file mode 100644
index 00000000..41fb852d
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Snackbar.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.TypedValue
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import com.arashafsharpour.daresaymovie.R
+import com.google.android.material.snackbar.Snackbar
+
+fun Snackbar.config(context: Context) {
+ val padding = context.resources.getDimensionPixelSize(R.dimen.spacing_2)
+ this.view.setPadding(this.view.paddingLeft, padding, this.view.paddingRight, padding)
+ this.view.background =
+ ContextCompat.getDrawable(context, R.drawable.shape_small_radius_background)
+ this.view.backgroundTintList =
+ ColorStateList.valueOf(ContextCompat.getColor(context, R.color.barsSnackBarA))
+
+ val textview =
+ this.view.findViewById(com.google.android.material.R.id.snackbar_text)
+ textview.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium)
+ textview.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ context.resources.getDimensionPixelSize(R.dimen.text_size_body2).toFloat()
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Text.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Text.kt
new file mode 100644
index 00000000..e91cce86
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/Text.kt
@@ -0,0 +1,36 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.databinding.BindingAdapter
+import java.text.NumberFormat
+import java.util.*
+
+@BindingAdapter("splitText")
+fun splitYears(view: AppCompatTextView, text: String?) {
+ view.text = text?.split("-")?.toTypedArray()?.get(0) ?: ""
+}
+
+@BindingAdapter("convertRate")
+fun convertRate(view: AppCompatTextView, rate: Float?) {
+ if (rate != null) {
+ view.text = (rate * 10).toInt().toString() + "%"
+ } else {
+ view.text = "0%"
+ }
+}
+
+@BindingAdapter("thousandSeparator")
+fun thousandSeparator(view: AppCompatTextView, budget: Long?) {
+ val filteredBudget = NumberFormat.getNumberInstance(Locale.US)
+ .format(budget)
+ view.text = "$filteredBudget$"
+}
+
+
+@BindingAdapter("convertDate")
+fun convertDate(view: AppCompatTextView, date: String) {
+ if (!date.isNullOrBlank()) {
+ view.text = date.split("T")[0]
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/View.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/View.kt
new file mode 100644
index 00000000..267057d9
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/extensions/View.kt
@@ -0,0 +1,37 @@
+package com.arashafsharpour.daresaymovie.infrastructure.extensions
+
+import android.annotation.SuppressLint
+import android.view.View
+import android.widget.ImageView
+import androidx.annotation.DimenRes
+import androidx.annotation.StringRes
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.google.android.material.snackbar.Snackbar
+
+fun ImageView.loadFromUrl(url: String) =
+ Glide.with(this.context.applicationContext)
+ .load(url)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .into(this)
+
+fun View.showSnackBar(
+ @StringRes message: Int,
+ duration: Int = Snackbar.LENGTH_SHORT
+) {
+ showSnackBar(context.getString(message), duration)
+}
+
+@SuppressLint("ShowToast")
+fun View.showSnackBar(
+ message: String,
+ duration: Int = Snackbar.LENGTH_SHORT
+) {
+ val snackbar = Snackbar.make(this, message, duration)
+ snackbar.config(context)
+ snackbar.show()
+}
+
+fun View.getDimensionPixelSize(@DimenRes dimenResId: Int): Int {
+ return context.resources.getDimensionPixelSize(dimenResId)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/MovieCategoryType.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/MovieCategoryType.kt
new file mode 100644
index 00000000..6ed92090
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/MovieCategoryType.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models
+
+enum class MovieCategoryType {
+ POPULAR,
+ UPCOMING,
+ NOWPLAYING,
+ TOPRATED,
+ FAVORITE,
+ WATCHLATER
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Account.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Account.kt
new file mode 100644
index 00000000..6b1cfb66
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Account.kt
@@ -0,0 +1,22 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Account(
+ @SerializedName("avatar")
+ var avatar: Avatar,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("iso_639_1")
+ var iso639: String,
+ @SerializedName("iso_3166_1")
+ var iso316: String,
+ @SerializedName("include_adult")
+ var includeAdult: Boolean,
+ @SerializedName("username")
+ var username: String,
+ @SerializedName("name")
+ var name: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/AuthorDetails.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/AuthorDetails.kt
new file mode 100644
index 00000000..36245671
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/AuthorDetails.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class AuthorDetails(
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("username")
+ var username: String,
+ @SerializedName("avatar_path")
+ var avatarPath: String?,
+ @SerializedName("rating")
+ var rating: Int?
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Avatar.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Avatar.kt
new file mode 100644
index 00000000..6bb06d67
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Avatar.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Avatar(
+ @SerializedName("gravatar")
+ var gravatar: Gravatar
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BackDrops.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BackDrops.kt
new file mode 100644
index 00000000..1957c198
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BackDrops.kt
@@ -0,0 +1,22 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class BackDrops(
+ @SerializedName("aspect_ratio")
+ var aspect_ratio: Float,
+ @SerializedName("file_path")
+ var filePath: String,
+ @SerializedName("height")
+ var height: Int,
+ @SerializedName("iso_639_1")
+ var iso: String?,
+ @SerializedName("vote_average")
+ var voteAverage: Float,
+ @SerializedName("vote_count")
+ var voteCount: Int,
+ @SerializedName("width")
+ var width: Int
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BelongsToCollection.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BelongsToCollection.kt
new file mode 100644
index 00000000..ca9bc452
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/BelongsToCollection.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class BelongsToCollection(
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("poster_path")
+ var posterPath: String,
+ @SerializedName("backdrop_path")
+ var backdropPath: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Cast.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Cast.kt
new file mode 100644
index 00000000..7a9a45c3
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Cast.kt
@@ -0,0 +1,49 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.recyclerview.widget.DiffUtil
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Cast(
+ @SerializedName("adult")
+ var adult: Boolean,
+ @SerializedName("gender")
+ var gender: Int?,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("known_for_department")
+ var knownForDepartment: String,
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("original_name")
+ var originalName: String,
+ @SerializedName("popularity")
+ var popularity: String,
+ @SerializedName("profile_path")
+ var profilePath: String?,
+ @SerializedName("character")
+ var character: String,
+ @SerializedName("credit_id")
+ var creditId: String,
+ @SerializedName("order")
+ var order: Int
+) {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: Cast,
+ newItem: Cast
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Cast,
+ newItem: Cast
+ ): Boolean {
+ return oldItem.hashCode() == newItem.hashCode()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Config.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Config.kt
new file mode 100644
index 00000000..ca4e423d
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Config.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.google.gson.annotations.SerializedName
+
+@Keep
+@Entity
+data class Config(
+ @PrimaryKey(autoGenerate = true) var key: Int,
+ var images: Images,
+ @SerializedName("change_keys")
+ var changeKeys: ArrayList
+) {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Credits.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Credits.kt
new file mode 100644
index 00000000..f5af646e
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Credits.kt
@@ -0,0 +1,15 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import java.util.ArrayList
+
+@Keep
+data class Credits(
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("cast")
+ var cast: ArrayList,
+ @SerializedName("crew")
+ var crew: ArrayList
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Crew.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Crew.kt
new file mode 100644
index 00000000..f3bd4221
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Crew.kt
@@ -0,0 +1,30 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Crew(
+ @SerializedName("adult")
+ var adult: Boolean,
+ @SerializedName("gender")
+ var gender: Int?,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("known_for_department")
+ var knownForDepartment: String,
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("original_name")
+ var originalName: String,
+ @SerializedName("popularity")
+ var popularity: String,
+ @SerializedName("profile_path")
+ var profilePath: String?,
+ @SerializedName("department")
+ var department: String,
+ @SerializedName("credit_id")
+ var creditId: String,
+ @SerializedName("job")
+ var job: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Genres.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Genres.kt
new file mode 100644
index 00000000..525441a5
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Genres.kt
@@ -0,0 +1,31 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.recyclerview.widget.DiffUtil
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Genres(
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("name")
+ var name: String
+) {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: Genres,
+ newItem: Genres
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Genres,
+ newItem: Genres
+ ): Boolean {
+ return oldItem.hashCode() == newItem.hashCode()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Gravatar.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Gravatar.kt
new file mode 100644
index 00000000..1d9dc8e7
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Gravatar.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Gravatar(
+ @SerializedName("")
+ var hash: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Images.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Images.kt
new file mode 100644
index 00000000..de70c454
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Images.kt
@@ -0,0 +1,24 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Images(
+ @SerializedName("base_url")
+ var baseurl: String,
+ @SerializedName("secure_base_url")
+ var secureBaseUrl: String,
+ @SerializedName("backdrop_sizes")
+ var backdropSizes: ArrayList,
+ @SerializedName("logo_sizes")
+ var logoSize: ArrayList,
+ @SerializedName("poster_size")
+ var posterSizes: ArrayList,
+ @SerializedName("profile_sizes")
+ var profileSizes: ArrayList,
+ @SerializedName("still_sizes")
+ var stillSizes: ArrayList
+) {
+
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Logos.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Logos.kt
new file mode 100644
index 00000000..5ac2be5e
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Logos.kt
@@ -0,0 +1,22 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Logos(
+ @SerializedName("aspect_ratio")
+ var aspect_ratio: Float,
+ @SerializedName("file_path")
+ var filePath: String,
+ @SerializedName("height")
+ var height: Int,
+ @SerializedName("iso_639_1")
+ var iso: String?,
+ @SerializedName("vote_average")
+ var voteAverage: Float,
+ @SerializedName("vote_count")
+ var voteCount: Int,
+ @SerializedName("width")
+ var width: Int
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movie.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movie.kt
new file mode 100644
index 00000000..c4a24078
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movie.kt
@@ -0,0 +1,55 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.recyclerview.widget.DiffUtil
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Movie(
+ @SerializedName("poster_path")
+ var posterPath: String?,
+ @SerializedName("adult")
+ var adult: Boolean,
+ @SerializedName("overview")
+ var overview: String,
+ @SerializedName("release_date")
+ var releaseDate: String,
+ @SerializedName("genre_ids")
+ var genreIds: ArrayList,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("original_title")
+ var originalTitle: String,
+ @SerializedName("original_language")
+ var originalLanguage: String,
+ @SerializedName("title")
+ var title: String,
+ @SerializedName("backdrop_path")
+ var backdropPath: String?,
+ @SerializedName("popularity")
+ var popularity: Float,
+ @SerializedName("vote_count")
+ var voteCount: Int,
+ @SerializedName("video")
+ var video: Boolean,
+ @SerializedName("vote_average")
+ var voteAverage: Float
+) {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: Movie,
+ newItem: Movie
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Movie,
+ newItem: Movie
+ ): Boolean {
+ return oldItem.hashCode() == newItem.hashCode()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieDetail.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieDetail.kt
new file mode 100644
index 00000000..a66799a2
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieDetail.kt
@@ -0,0 +1,59 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import java.util.ArrayList
+
+@Keep
+data class MovieDetail(
+ @SerializedName("adult")
+ var adult: Boolean,
+ @SerializedName("backdrop_path")
+ var backdropPath: String?,
+ @SerializedName("belongs_to_collection")
+ var belongsToCollection: BelongsToCollection?,
+ @SerializedName("budget")
+ var budget: Long,
+ @SerializedName("genres")
+ var genres: ArrayList,
+ @SerializedName("homepage")
+ var homePage: String,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("imdb_id")
+ var imdbId: String?,
+ @SerializedName("original_language")
+ var originalLanguage: String,
+ @SerializedName("original_title")
+ var originalTitle: String,
+ @SerializedName("overview")
+ var overView: String,
+ @SerializedName("popularity")
+ var popularity: Float,
+ @SerializedName("poster_path")
+ var posterPath: String?,
+ @SerializedName("production_companies")
+ var productionCompanies: ArrayList,
+ @SerializedName("production_countries")
+ var productionCountries: ArrayList,
+ @SerializedName("release_date")
+ var releaseDate: String,
+ @SerializedName("revenue")
+ var revenue: Long,
+ @SerializedName("runtime")
+ var runtime: Int?,
+ @SerializedName("spoken_languages")
+ var spokenLanguages: ArrayList,
+ @SerializedName("status")
+ var status: String,
+ @SerializedName("tagline")
+ var tagline: String?,
+ @SerializedName("title")
+ var title: String,
+ @SerializedName("video")
+ var video: String,
+ @SerializedName("vote_average")
+ var voteAverage: Float,
+ @SerializedName("vote_count")
+ var voteCount: Int
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieImages.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieImages.kt
new file mode 100644
index 00000000..b2ce505c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/MovieImages.kt
@@ -0,0 +1,17 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import java.util.ArrayList
+
+@Keep
+data class MovieImages(
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("backdrops")
+ var backdrops: ArrayList,
+ @SerializedName("posters")
+ var posters: ArrayList,
+ @SerializedName("logos")
+ var logos: ArrayList
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movies.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movies.kt
new file mode 100644
index 00000000..d8d02b90
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Movies.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Movies(
+ @SerializedName("page")
+ var page: Int,
+ @SerializedName("results")
+ var result: ArrayList,
+ @SerializedName("total_pages")
+ var totalPages: Int,
+ @SerializedName("total_results")
+ var totalResults: Int
+){
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/PagingResponse.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/PagingResponse.kt
new file mode 100644
index 00000000..ab9319c8
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/PagingResponse.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.fasterxml.jackson.annotation.JsonProperty
+
+@Keep
+data class PagingResponse(
+ @field:JsonProperty var maximum: Int,
+ @field:JsonProperty var resultItems: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Posters.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Posters.kt
new file mode 100644
index 00000000..d663bb68
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Posters.kt
@@ -0,0 +1,41 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.recyclerview.widget.DiffUtil
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class Posters(
+ @SerializedName("aspect_ratio")
+ var aspect_ratio: Float,
+ @SerializedName("file_path")
+ var filePath: String,
+ @SerializedName("height")
+ var height: Int,
+ @SerializedName("iso_639_1")
+ var iso: String?,
+ @SerializedName("vote_average")
+ var voteAverage: Float,
+ @SerializedName("vote_count")
+ var voteCount: Int,
+ @SerializedName("width")
+ var width: Int
+) {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: Posters,
+ newItem: Posters
+ ): Boolean {
+ return oldItem.filePath == newItem.filePath
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Posters,
+ newItem: Posters
+ ): Boolean {
+ return oldItem.hashCode() == newItem.hashCode()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCompanies.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCompanies.kt
new file mode 100644
index 00000000..ee985666
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCompanies.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class ProductionCompanies(
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("logo_path")
+ var logoPath: String?,
+ @SerializedName("origin_country")
+ var originCountry: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCountries.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCountries.kt
new file mode 100644
index 00000000..6f488ad7
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ProductionCountries.kt
@@ -0,0 +1,12 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class ProductionCountries(
+ @SerializedName("iso_3166_1")
+ var iso: String,
+ @SerializedName("name")
+ var name: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Review.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Review.kt
new file mode 100644
index 00000000..ddc5fd26
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/Review.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import java.util.ArrayList
+
+@Keep
+data class Review(
+ @SerializedName("id")
+ var id: Int,
+ @SerializedName("page")
+ var page: Int,
+ @SerializedName("results")
+ var results: ArrayList,
+ @SerializedName("total_pages")
+ var totalPages: Int,
+ @SerializedName("total_results")
+ var totalResults: Int
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ReviewResults.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ReviewResults.kt
new file mode 100644
index 00000000..81fa7be6
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/ReviewResults.kt
@@ -0,0 +1,41 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import androidx.recyclerview.widget.DiffUtil
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class ReviewResults(
+ @SerializedName("author")
+ var author: String,
+ @SerializedName("author_details")
+ var authorDetails: AuthorDetails,
+ @SerializedName("content")
+ var content: String,
+ @SerializedName("created_at")
+ var createdAt: String,
+ @SerializedName("id")
+ var id: String,
+ @SerializedName("updated_at")
+ var updatedAt: String,
+ @SerializedName("url")
+ var url: String
+) {
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: ReviewResults,
+ newItem: ReviewResults
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: ReviewResults,
+ newItem: ReviewResults
+ ): Boolean {
+ return oldItem.hashCode() == newItem.hashCode()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/SpokenLanguages.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/SpokenLanguages.kt
new file mode 100644
index 00000000..c26e3674
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/models/contracts/SpokenLanguages.kt
@@ -0,0 +1,14 @@
+package com.arashafsharpour.daresaymovie.infrastructure.models.contracts
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class SpokenLanguages(
+ @SerializedName("iso_639_1")
+ var iso: String,
+ @SerializedName("name")
+ var name: String,
+ @SerializedName("english_name")
+ var englishName: String
+)
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiCallHeaderInterceptor.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiCallHeaderInterceptor.kt
new file mode 100644
index 00000000..6307b37e
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiCallHeaderInterceptor.kt
@@ -0,0 +1,33 @@
+package com.arashafsharpour.daresaymovie.infrastructure.network
+
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.policy.NetworkTimeoutExceptionPolicy
+import com.arashafsharpour.daresaymovie.persistence.USER_TOKEN
+import okhttp3.Interceptor
+import okhttp3.Protocol
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+
+class ApiCallHeaderInterceptor() : Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ try {
+ val builder: Request.Builder = request.newBuilder()
+ val contentType = request.header("Content-Type") ?: "application/json"
+
+ builder.addHeader("Accept", "application/json")
+ builder.addHeader("Content-Type", contentType)
+ builder.addHeader("Authorization", USER_TOKEN)
+ return chain.proceed(builder.build())
+ } catch (t: Throwable) {
+ return Response.Builder()
+ .request(request)
+ .code(NetworkTimeoutExceptionPolicy().getHttpCode())
+ .protocol(Protocol.HTTP_1_1)
+ .body("{Time out}".toResponseBody(null))
+ .message("Time out")
+ .build()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiClientBuilder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiClientBuilder.kt
new file mode 100644
index 00000000..27e2c2e4
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/network/ApiClientBuilder.kt
@@ -0,0 +1,41 @@
+package com.arashafsharpour.daresaymovie.infrastructure.network
+
+import android.util.Log
+import timber.log.Timber
+
+class ApiClientBuilder(private val apiCall: suspend () -> T) {
+
+ private var _onError: ((java.lang.Exception) -> Unit)? = null
+ private var _beforeCall: (() -> Unit)? = null
+ private var _afterCall: (() -> Unit)? = null
+ private var _onSuccess: ((T) -> Unit)? = null
+
+ fun onSuccess(callback: (T) -> Unit): ApiClientBuilder = apply {
+ _onSuccess = callback
+ }
+
+ fun onError(callback: (java.lang.Exception) -> Unit): ApiClientBuilder = apply {
+ _onError = callback
+ }
+
+ fun beforeCall(callback: () -> Unit): ApiClientBuilder = apply {
+ _beforeCall = callback
+ }
+
+ fun afterCall(callback: () -> Unit): ApiClientBuilder = apply {
+ _afterCall = callback
+ }
+
+ suspend fun call() {
+ _beforeCall?.invoke()
+ try {
+ val response = apiCall.invoke()
+ _onSuccess?.invoke(response)
+ } catch (e: Exception) {
+ Timber.e(e)
+ _onError?.invoke(e)
+ }
+ _afterCall?.invoke()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseActivity.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseActivity.kt
new file mode 100644
index 00000000..6630f325
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseActivity.kt
@@ -0,0 +1,59 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.graphics.Rect
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavController
+import androidx.navigation.fragment.NavHostFragment
+import androidx.window.layout.WindowMetricsCalculator
+import com.arashafsharpour.daresaymovie.R
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.observeActions
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+abstract class BaseActivity : AppCompatActivity() {
+
+ lateinit var navController: NavController
+
+ abstract val viewModel: VM
+ abstract val layoutRes: Int
+ abstract val navigationId: Int
+ abstract val hasNavigation: Boolean
+ lateinit var binding: VDB
+ var currentBounds = MutableLiveData()
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, layoutRes) as VDB
+ initializeBindingVariables()
+ initializeNavigator()
+ getScreenHeightAndWidth()
+ initializeActions()
+ }
+
+ private fun initializeActions() = observeActions(viewModel)
+
+ protected abstract fun initializeBindingVariables()
+ protected open fun subscribeSpecificObservables() {}
+
+ open fun initializeNavigator() {
+ if (hasNavigation) {
+ navController = (supportFragmentManager.findFragmentById(R.id.main_nav_host) as NavHostFragment).navController
+ val navHostFragment =
+ supportFragmentManager.findFragmentById(navigationId) as NavHostFragment?
+ navController = navHostFragment!!.navController
+ }
+ }
+
+ private fun getScreenHeightAndWidth() {
+ lifecycleScope.launch(Dispatchers.Main) {
+ val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this@BaseActivity)
+ currentBounds.value = windowMetrics.bounds // E.g. [0 0 1350 1800]
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseAdapter.kt
new file mode 100644
index 00000000..0b779e2a
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseAdapter.kt
@@ -0,0 +1,63 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.recyclerview.widget.DiffUtil
+
+abstract class BaseAdapter(
+ diffCallback: DiffUtil.ItemCallback
+) : BaseListAdapter.DataBindingViewHolder>(diffCallback) {
+
+ var onItemClicked: ((item: T, position: Int, view: View) -> Unit)? = null
+ var onBindItem: ((binding: DB, item: T) -> Unit)? = null
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder {
+ return DataBindingViewHolder(
+ DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ viewType,
+ parent,
+ false
+ ) as DB
+ )
+ }
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) =
+ holder.bind(getItem(position))
+
+ abstract override fun getItemViewType(position: Int): Int
+
+ abstract fun getItemVariableId(): Int
+
+ inner class DataBindingViewHolder(private val binding: DB) : BaseViewHolder(binding) {
+
+ override val lifecycleRegistry = LifecycleRegistry(this)
+
+ init {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return lifecycleRegistry
+ }
+
+ fun bind(item: T) {
+ binding.apply {
+ lifecycleOwner = this@DataBindingViewHolder
+ setVariable(getItemVariableId(), item)
+ executePendingBindings()
+ onBindItem?.invoke(binding, item)
+ root.apply {
+ setOnClickListener {
+ onItemClicked?.invoke(item, bindingAdapterPosition, this)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseBottomSheetDialogFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseBottomSheetDialogFragment.kt
new file mode 100644
index 00000000..260fc4ea
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseBottomSheetDialogFragment.kt
@@ -0,0 +1,70 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.DialogFragment
+import com.arashafsharpour.daresaymovie.R
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+
+abstract class BaseBottomSheetDialogFragment :
+ BottomSheetDialogFragment() {
+
+ protected abstract val layoutResourceId: Int
+ protected open lateinit var binding: DB
+ protected abstract val viewModel: VM
+ private lateinit var dialog: BottomSheetDialog
+ private lateinit var behavior: BottomSheetBehavior
+
+ protected abstract fun initializeBindingVariables()
+ protected open fun subscribeSpecificObservables() {}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setBottomSheetDialogStyle()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
+ setupBottomSheetDialogScrollable()
+ return dialog
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+ binding = DataBindingUtil.inflate(inflater, layoutResourceId, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ initializeBindingVariables()
+ subscribeSpecificObservables()
+ return binding.root
+ }
+
+ private fun setBottomSheetDialogStyle() {
+ setStyle(
+ DialogFragment.STYLE_NORMAL,
+ R.style.BottomSheetDialogStyle
+ )
+ }
+
+ private fun setupBottomSheetDialogScrollable() {
+ dialog.setOnShowListener { dialog ->
+ val bottomSheetDialog = dialog as BottomSheetDialog
+ val designBottomSheet =
+ bottomSheetDialog.findViewById(R.id.design_bottom_sheet) as View
+ behavior = BottomSheetBehavior.from(designBottomSheet)
+ behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ behavior.isDraggable = false
+ behavior.isHideable = false
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseFragment.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseFragment.kt
new file mode 100644
index 00000000..006fb04b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseFragment.kt
@@ -0,0 +1,85 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.MutableLiveData
+import androidx.navigation.NavController
+import androidx.navigation.Navigation
+import androidx.navigation.fragment.findNavController
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.CurrentFragment
+
+
+abstract class BaseFragment : Fragment() {
+
+ protected abstract val layoutResourceId: Int
+ protected open lateinit var binding: DB
+ protected abstract val viewModel: VM
+ lateinit var navController: NavController
+ var currentBounds = MutableLiveData()
+
+ open fun init() {}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ navController = findNavController()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ super.onCreateView(inflater, container, savedInstanceState)
+
+ val shouldAttachToParent = false
+ binding = DataBindingUtil.inflate(
+ inflater,
+ layoutResourceId,
+ container,
+ shouldAttachToParent
+ )
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.lifecycleOwner = viewLifecycleOwner
+ initializeBindingVariables()
+ }
+
+ open fun navigate(action: Int) {
+ view?.let { _view ->
+ Navigation.findNavController(_view).navigate(action)
+ }
+ }
+ protected abstract fun initializeBindingVariables()
+ open fun onNetworkStateChanged(connected: Boolean) {}
+// protected open fun setCurrentFragmentAsAmbient() {
+// CurrentFragment.currentClass = this::class.java
+// CurrentFragment.data = mapOf()
+// }
+
+ private fun getBackStackEntry(entryKey: String): T? {
+ return navController.currentBackStackEntry?.savedStateHandle?.get(entryKey)
+ }
+
+ fun getAndRemoveBackStackEntry(entryKey: String): T? {
+ val result = getBackStackEntry(entryKey)
+ removeBackStackEntry(entryKey)
+ return result
+ }
+
+ private fun removeBackStackEntry(entryKey: String) {
+ navController.currentBackStackEntry?.savedStateHandle?.remove(entryKey)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseListAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseListAdapter.kt
new file mode 100644
index 00000000..3feedc19
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseListAdapter.kt
@@ -0,0 +1,23 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.util.Log
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+
+abstract class BaseListAdapter
+ (diffCallback: DiffUtil.ItemCallback) : ListAdapter(diffCallback) {
+
+ override fun submitList(list: MutableList?) {
+ super.submitList(if (list != null) ArrayList(list).toMutableList() else null)
+ }
+
+ override fun onViewAttachedToWindow(holder: VH) {
+ super.onViewAttachedToWindow(holder)
+ holder.onAppear()
+ }
+
+ override fun onViewDetachedFromWindow(holder: VH) {
+ super.onViewDetachedFromWindow(holder)
+ holder.onDisappear()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseSimpleListAdapter.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseSimpleListAdapter.kt
new file mode 100644
index 00000000..10c7a1e5
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseSimpleListAdapter.kt
@@ -0,0 +1,63 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.recyclerview.widget.DiffUtil
+
+abstract class BaseSimpleListAdapter(
+ diffCallback: DiffUtil.ItemCallback
+) : BaseListAdapter.DataBindingViewHolder>(diffCallback) {
+
+ var onItemClicked: ((item: T, position: Int, view: View) -> Unit)? = null
+ var onBindItem: ((binding: DB, item: T) -> Unit)? = null
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder {
+ return DataBindingViewHolder(
+ DataBindingUtil.inflate(
+ LayoutInflater.from(parent.context),
+ viewType,
+ parent,
+ false
+ ) as DB
+ )
+ }
+
+ override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) =
+ holder.bind(getItem(position))
+
+ abstract override fun getItemViewType(position: Int): Int
+
+ abstract fun getItemVariableId(): Int
+
+ inner class DataBindingViewHolder(private val binding: DB) : BaseViewHolder(binding) {
+
+ override val lifecycleRegistry = LifecycleRegistry(this)
+
+ init {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return lifecycleRegistry
+ }
+
+ fun bind(item: T) {
+ binding.apply {
+ lifecycleOwner = this@DataBindingViewHolder
+ setVariable(getItemVariableId(), item)
+ executePendingBindings()
+ onBindItem?.invoke(binding, item)
+ root.apply {
+ setOnClickListener {
+ onItemClicked?.invoke(item, bindingAdapterPosition, this)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewHolder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewHolder.kt
new file mode 100644
index 00000000..d6ba9b6f
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewHolder.kt
@@ -0,0 +1,32 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.cancelChildren
+
+abstract class BaseViewHolder(binding: ViewDataBinding) :
+ RecyclerView.ViewHolder(binding.root), LifecycleOwner {
+
+ abstract val lifecycleRegistry: LifecycleRegistry
+
+ open fun onAppear() {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ }
+
+ open fun onDisappear() {
+ lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ lifecycleScope.coroutineContext.cancelChildren()
+ }
+
+ open fun setVariable(item: Any?) {
+
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return lifecycleRegistry
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewModel.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewModel.kt
new file mode 100644
index 00000000..2e476901
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/BaseViewModel.kt
@@ -0,0 +1,15 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import androidx.lifecycle.ViewModel
+import com.arashafsharpour.daresaymovie.infrastructure.coordinator.ICoordinator
+import com.arashafsharpour.daresaymovie.infrastructure.exceptions.exceptionHandling.ExceptionHandler
+
+abstract class BaseViewModel(private val coordinator: ICoordinator) : ViewModel() {
+
+ abstract val exceptionHandler: ExceptionHandler
+
+ fun getPrimitiveCoordinator(): ICoordinator {
+ return coordinator
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/NetworkHandler.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/NetworkHandler.kt
new file mode 100644
index 00000000..d710a66a
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/infrastructure/platform/NetworkHandler.kt
@@ -0,0 +1,37 @@
+package com.arashafsharpour.daresaymovie.infrastructure.platform
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.os.Build
+import com.arashafsharpour.daresaymovie.infrastructure.extensions.connectivityManager
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class NetworkHandler
+@Inject constructor(@ApplicationContext private val context: Context) {
+
+ fun isNetworkAvailable(): Boolean {
+ val connectivityManager = context.connectivityManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val network = connectivityManager.activeNetwork ?: return false
+ val activeNetwork =
+ connectivityManager.getNetworkCapabilities(network) ?: return false
+
+ return when {
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
+ activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
+ else -> false
+ }
+ } else {
+ @Suppress("DEPRECATION") val networkInfo =
+ connectivityManager.activeNetworkInfo ?: return false
+ @Suppress("DEPRECATION")
+ return networkInfo.isConnected
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/Constants.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/Constants.kt
new file mode 100644
index 00000000..8976a968
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/Constants.kt
@@ -0,0 +1,15 @@
+package com.arashafsharpour.daresaymovie.persistence
+
+const val USER_TOKEN = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2NzI0NDczZjhhNWZkOTU4MTVjMWMxYmU5N2YzZjg3ZSIsInN1YiI6IjYxYzBjZWY2YmZlYjhiMDA4ZTUxYzgyZCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.SiuLi1OaseRl3RMEkW3Ktq0Zr5lB2flc6StTdRuW-WI"
+
+const val API_KEY = "6724473f8a5fd95815c1c1be97f3f87e"
+
+const val TIMEOUT: Long = 15
+
+const val CONFIG_KEY = "_CONFIG_KEY"
+
+const val BASE_URL_KEY = "_BASE_URL_KEY"
+
+const val BASE_IMAGE_URL = "https://image.tmdb.org/t/p"
+
+const val MOVIE_REVIEW_DIALOG_TAG = "_MOVIE_REVIEW_DIALOG_TAG"
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/DaresayMovieDatabase.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/DaresayMovieDatabase.kt
new file mode 100644
index 00000000..7e9f67b1
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/DaresayMovieDatabase.kt
@@ -0,0 +1,5 @@
+package com.arashafsharpour.daresaymovie.persistence.database.roomdatabase
+
+import androidx.room.RoomDatabase
+
+abstract class DaresayMovieDatabase : RoomDatabase()
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/RoomConverters.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/RoomConverters.kt
new file mode 100644
index 00000000..c51aa93f
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/RoomConverters.kt
@@ -0,0 +1,4 @@
+package com.arashafsharpour.daresaymovie.persistence.database.roomdatabase
+
+class RoomConverters {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/dao/IConfigDao.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/dao/IConfigDao.kt
new file mode 100644
index 00000000..8e12fc5e
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/roomdatabase/dao/IConfigDao.kt
@@ -0,0 +1,19 @@
+package com.arashafsharpour.daresaymovie.persistence.database.roomdatabase.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy.REPLACE
+import androidx.room.Query
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Config
+
+@Dao
+interface IConfigDao {
+ @Query("SELECT * FROM Config LIMIT 1;")
+ suspend fun getConfig(): Config?
+
+ @Insert(onConflict = REPLACE)
+ fun save(config: Config)
+
+ @Query("DELETE FROM Config;")
+ fun clear()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/ISharedPreferencesHelper.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/ISharedPreferencesHelper.kt
new file mode 100644
index 00000000..5d5b6148
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/ISharedPreferencesHelper.kt
@@ -0,0 +1,13 @@
+package com.arashafsharpour.daresaymovie.persistence.database.sharedpreference
+
+import kotlinx.coroutines.CoroutineScope
+
+interface ISharedPreferencesHelper {
+ fun setValue(key: String, value: Any?, scope: CoroutineScope? = null)
+ fun getString(key: String, defValue: String = ""): String
+ fun getInt(key: String, defValue: Int = -1): Int
+ fun getBoolean(key: String, defValue: Boolean = false): Boolean
+ fun getFloat(key: String, defValue: Float = -1f): Float
+ fun getLong(key: String, defValue: Long = -1L): Long
+ fun clearSharedPreferences()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/SharedPreferencesHelper.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/SharedPreferencesHelper.kt
new file mode 100644
index 00000000..96bc9e16
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/database/sharedpreference/SharedPreferencesHelper.kt
@@ -0,0 +1,58 @@
+package com.arashafsharpour.daresaymovie.persistence.database.sharedpreference
+
+import android.content.SharedPreferences
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class SharedPreferencesHelper(private val sharedPreferences: SharedPreferences) :
+ ISharedPreferencesHelper {
+
+ private val editor: SharedPreferences.Editor = sharedPreferences.edit()
+
+ override fun setValue(key: String, value: Any?, scope: CoroutineScope?) {
+ if (scope != null) {
+ scope.launch(Dispatchers.IO) {
+ putValue(key, value)
+ }
+ } else {
+ putValue(key, value)
+ }
+ }
+
+ override fun getString(key: String, defValue: String): String {
+ return sharedPreferences.getString(key, defValue) ?: ""
+ }
+
+ override fun getInt(key: String, defValue: Int): Int {
+ return sharedPreferences.getInt(key, defValue)
+ }
+
+ override fun getBoolean(key: String, defValue: Boolean): Boolean {
+ return sharedPreferences.getBoolean(key, defValue)
+ }
+
+ override fun getFloat(key: String, defValue: Float): Float {
+ return sharedPreferences.getFloat(key, defValue)
+ }
+
+ override fun getLong(key: String, defValue: Long): Long {
+ return sharedPreferences.getLong(key, defValue)
+ }
+
+ override fun clearSharedPreferences() {
+ editor.clear().commit()
+ }
+
+ private fun putValue(key: String, value: Any?) {
+ when (value) {
+ is String -> editor.putString(key, value)
+ is Int -> editor.putInt(key, value)
+ is Boolean -> editor.putBoolean(key, value)
+ is Float -> editor.putFloat(key, value)
+ is Long -> editor.putLong(key, value)
+ else -> throw UnsupportedOperationException("Not yet implemented")
+ }
+ editor.commit()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/DatabaseModule.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/DatabaseModule.kt
new file mode 100644
index 00000000..00c213a6
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/DatabaseModule.kt
@@ -0,0 +1,31 @@
+package com.arashafsharpour.daresaymovie.persistence.di
+
+import android.content.Context
+import androidx.room.Room
+import com.arashafsharpour.daresaymovie.persistence.database.roomdatabase.DaresayMovieDatabase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class DatabaseModule {
+
+ @Singleton
+ @Provides
+ fun provideAppDatabase(@ApplicationContext appContext: Context): DaresayMovieDatabase {
+ return Room.databaseBuilder(
+ appContext,
+ DaresayMovieDatabase::class.java,
+ "DaresayMovie"
+ ).fallbackToDestructiveMigration().build()
+ }
+
+// @Provides
+// fun provideConfigDao(database: DaresayMovieDatabase): IConfigDao {
+// return database.configDao
+// }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/LocalStorageModuleProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/LocalStorageModuleProvider.kt
new file mode 100644
index 00000000..77d34c64
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/di/LocalStorageModuleProvider.kt
@@ -0,0 +1,26 @@
+package com.arashafsharpour.daresaymovie.persistence.di
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+import com.arashafsharpour.daresaymovie.persistence.database.sharedpreference.ISharedPreferencesHelper
+import com.arashafsharpour.daresaymovie.persistence.database.sharedpreference.SharedPreferencesHelper
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class LocalStorageModuleProvider {
+
+ @Provides
+ @Singleton
+ fun provideSharedPreferences(@ApplicationContext context: Context): ISharedPreferencesHelper {
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+ return SharedPreferencesHelper(
+ sharedPreferences
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigModuleProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigModuleProvider.kt
new file mode 100644
index 00000000..ddb16a2d
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigModuleProvider.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.config
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class ConfigModuleProvider {
+
+ @Provides
+ @Singleton
+ fun provideConfigRepository(dataSource: ConfigRepository) : IConfigRepository = dataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigRepository.kt
new file mode 100644
index 00000000..fe8ff77b
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/ConfigRepository.kt
@@ -0,0 +1,35 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.config
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Config
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+import com.arashafsharpour.daresaymovie.persistence.BASE_URL_KEY
+import com.arashafsharpour.daresaymovie.persistence.CONFIG_KEY
+import com.arashafsharpour.daresaymovie.persistence.database.sharedpreference.ISharedPreferencesHelper
+import com.google.gson.Gson
+import javax.inject.Inject
+
+class ConfigRepository @Inject constructor(
+ private val configApi: IConfigApi,
+ private val sharedPrefHelper: ISharedPreferencesHelper
+ ): IConfigRepository {
+
+ override fun getConfig(): ApiClientBuilder {
+ return ApiClientBuilder { configApi.getConfigurations() }
+ }
+
+ override fun saveConfigLocally(config: Config): Boolean {
+ sharedPrefHelper.setValue(CONFIG_KEY, Gson().toJson(config))
+ sharedPrefHelper.setValue(BASE_URL_KEY, config.images.secureBaseUrl)
+ return true
+ }
+
+ override fun loadConfigLocally(): Config? {
+ sharedPrefHelper.getString(CONFIG_KEY, "").apply {
+ return if (this.isNotBlank()) {
+ Gson().fromJson(this, Config::class.java)
+ } else {
+ null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigApi.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigApi.kt
new file mode 100644
index 00000000..3b79a445
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigApi.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.config
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Config
+import retrofit2.http.GET
+
+interface IConfigApi {
+
+ @GET("/3/configuration")
+ suspend fun getConfigurations(): Config
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigRepository.kt
new file mode 100644
index 00000000..58e96b5c
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/config/IConfigRepository.kt
@@ -0,0 +1,10 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.config
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Config
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+
+interface IConfigRepository {
+ fun getConfig(): ApiClientBuilder
+ fun saveConfigLocally(config: Config): Boolean
+ fun loadConfigLocally(): Config?
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailApi.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailApi.kt
new file mode 100644
index 00000000..0137848d
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailApi.kt
@@ -0,0 +1,32 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Credits
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieDetail
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieImages
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Review
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface IMovieDetailApi {
+ @GET("/3/movie/{movie_id}")
+ suspend fun getPopularMovies(
+ @Path("movie_id") movieId: String
+ ): MovieDetail
+
+ @GET("/3/movie/{movie_id}/credits")
+ suspend fun getCredit(
+ @Path("movie_id") movieId: String
+ ): Credits
+
+ @GET("/3/movie/{movie_id}/reviews")
+ suspend fun getReviews(
+ @Path("movie_id") movieId: String,
+ @Query("page") page: Int
+ ): Review
+
+ @GET("/3/movie/{movie_id}/images")
+ suspend fun getImages(
+ @Path("movie_id") movieId: String
+ ): MovieImages
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailRepository.kt
new file mode 100644
index 00000000..0bd6bc9d
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/IMovieDetailRepository.kt
@@ -0,0 +1,14 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Credits
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieDetail
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieImages
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Review
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+
+interface IMovieDetailRepository {
+ fun getMovieDetail(movieId: String) : ApiClientBuilder
+ fun getCredits(movieId: String) : ApiClientBuilder
+ fun getReviews(movieId: String, page: Int = 1) : ApiClientBuilder
+ fun getImages(movieId: String) : ApiClientBuilder
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailModuleBinder.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailModuleBinder.kt
new file mode 100644
index 00000000..97877b15
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailModuleBinder.kt
@@ -0,0 +1,15 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class MovieDetailModuleBinder {
+ @Provides
+ @Singleton
+ fun provideConfigRepository(dataSource: MovieDetailRepository) : IMovieDetailRepository = dataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailRepository.kt
new file mode 100644
index 00000000..54541145
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/moviedetail/MovieDetailRepository.kt
@@ -0,0 +1,29 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.moviedetail
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Credits
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieDetail
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.MovieImages
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Review
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+import javax.inject.Inject
+
+class MovieDetailRepository @Inject constructor(
+ private val movieDetailApi: IMovieDetailApi
+): IMovieDetailRepository {
+
+ override fun getMovieDetail(movieId: String): ApiClientBuilder {
+ return ApiClientBuilder { movieDetailApi.getPopularMovies(movieId) }
+ }
+
+ override fun getCredits(movieId: String): ApiClientBuilder {
+ return ApiClientBuilder { movieDetailApi.getCredit(movieId) }
+ }
+
+ override fun getReviews(movieId: String, page: Int): ApiClientBuilder {
+ return ApiClientBuilder { movieDetailApi.getReviews(movieId, page) }
+ }
+
+ override fun getImages(movieId: String): ApiClientBuilder {
+ return ApiClientBuilder { movieDetailApi.getImages(movieId) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesApi.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesApi.kt
new file mode 100644
index 00000000..b417ecb4
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesApi.kt
@@ -0,0 +1,27 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.movies
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface IMoviesApi {
+ @GET("/3/movie/popular")
+ suspend fun getPopularMovies(
+ @Query("page") page: Int
+ ): Movies
+
+ @GET("/3/movie/now_playing")
+ suspend fun getNowPlayingMovies(
+ @Query("page") page: Int
+ ): Movies
+
+ @GET("/3/movie/top_rated")
+ suspend fun getTopRatedMovies(
+ @Query("page") page: Int
+ ): Movies
+
+ @GET("/3/movie/upcoming")
+ suspend fun getUpcomingMovies(
+ @Query("page") page: Int
+ ): Movies
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesRepository.kt
new file mode 100644
index 00000000..015ddd35
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/IMoviesRepository.kt
@@ -0,0 +1,9 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.movies
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+
+interface IMoviesRepository {
+ fun getMoviesByType(categoryType: MovieCategoryType, page: Int) : ApiClientBuilder
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesModuleProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesModuleProvider.kt
new file mode 100644
index 00000000..9f033a19
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesModuleProvider.kt
@@ -0,0 +1,16 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.movies
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class MoviesModuleProvider {
+
+ @Provides
+ @Singleton
+ fun provideConfigRepository(dataSource: MoviesRepository): IMoviesRepository = dataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesRepository.kt
new file mode 100644
index 00000000..970a8939
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/movies/MoviesRepository.kt
@@ -0,0 +1,26 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.movies
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+import javax.inject.Inject
+
+class MoviesRepository @Inject constructor(
+ private val moviesApi: IMoviesApi
+): IMoviesRepository {
+
+ override fun getMoviesByType(categoryType: MovieCategoryType, page: Int): ApiClientBuilder {
+ return when(categoryType) {
+ MovieCategoryType.POPULAR -> {
+ ApiClientBuilder { moviesApi.getPopularMovies(page) }
+ }
+ MovieCategoryType.NOWPLAYING -> {
+ ApiClientBuilder { moviesApi.getNowPlayingMovies(page) }
+ }
+ MovieCategoryType.UPCOMING -> {
+ ApiClientBuilder { moviesApi.getUpcomingMovies(page) }
+ }
+ else -> { ApiClientBuilder { moviesApi.getTopRatedMovies(page) } }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileApi.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileApi.kt
new file mode 100644
index 00000000..58085bd4
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileApi.kt
@@ -0,0 +1,23 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.profile
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Account
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface IProfileApi {
+ @GET("/3/account")
+ suspend fun getProfile(
+ @Query("api_key")apiKey: String
+ ): Account
+
+ @GET("/3/movie/500/recommendations")
+ suspend fun getFavoriteMovies(
+ @Query("page") page: Int
+ ): Movies
+
+ @GET("/3/movie/550/recommendations")
+ suspend fun getWatchLaterMovies(
+ @Query("page") page: Int
+ ): Movies
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileRepository.kt
new file mode 100644
index 00000000..ad1456a7
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/IProfileRepository.kt
@@ -0,0 +1,11 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.profile
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Account
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+
+interface IProfileRepository {
+ fun getProfile(apiKey: String): ApiClientBuilder
+ fun getMoviesByType(categoryType: MovieCategoryType, page: Int) : ApiClientBuilder
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileModuleProvider.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileModuleProvider.kt
new file mode 100644
index 00000000..7257eb17
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileModuleProvider.kt
@@ -0,0 +1,15 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.profile
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class ProfileModuleProvider {
+ @Provides
+ @Singleton
+ fun provideConfigRepository(dataSource: ProfileRepository): IProfileRepository = dataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileRepository.kt b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileRepository.kt
new file mode 100644
index 00000000..b1c7dd0f
--- /dev/null
+++ b/app/src/main/java/com/arashafsharpour/daresaymovie/persistence/repositories/profile/ProfileRepository.kt
@@ -0,0 +1,29 @@
+package com.arashafsharpour.daresaymovie.persistence.repositories.profile
+
+import com.arashafsharpour.daresaymovie.infrastructure.models.MovieCategoryType
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Account
+import com.arashafsharpour.daresaymovie.infrastructure.models.contracts.Movies
+import com.arashafsharpour.daresaymovie.infrastructure.network.ApiClientBuilder
+import javax.inject.Inject
+
+class ProfileRepository @Inject constructor(
+ private val profileApi: IProfileApi
+) : IProfileRepository {
+ override fun getProfile(apiKey: String): ApiClientBuilder {
+ return ApiClientBuilder { profileApi.getProfile(apiKey) }
+ }
+
+ override fun getMoviesByType(
+ categoryType: MovieCategoryType,
+ page: Int
+ ): ApiClientBuilder {
+ return when (categoryType) {
+ MovieCategoryType.WATCHLATER -> {
+ ApiClientBuilder { profileApi.getWatchLaterMovies(page) }
+ }
+ else -> {
+ return ApiClientBuilder { profileApi.getFavoriteMovies(page) }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml
new file mode 100644
index 00000000..152a06c4
--- /dev/null
+++ b/app/src/main/res/anim/slide_down.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml
new file mode 100644
index 00000000..8c2c21ef
--- /dev/null
+++ b/app/src/main/res/anim/slide_in_left.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml
new file mode 100644
index 00000000..c6a4f61e
--- /dev/null
+++ b/app/src/main/res/anim/slide_in_right.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/anim/slide_out.xml b/app/src/main/res/anim/slide_out.xml
new file mode 100644
index 00000000..1970c662
--- /dev/null
+++ b/app/src/main/res/anim/slide_out.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml
new file mode 100644
index 00000000..b4be2775
--- /dev/null
+++ b/app/src/main/res/anim/slide_out_left.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml
new file mode 100644
index 00000000..891007f5
--- /dev/null
+++ b/app/src/main/res/anim/slide_out_right.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_out_splash.xml b/app/src/main/res/anim/slide_out_splash.xml
new file mode 100644
index 00000000..535d8ed7
--- /dev/null
+++ b/app/src/main/res/anim/slide_out_splash.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml
new file mode 100644
index 00000000..5ea1cb11
--- /dev/null
+++ b/app/src/main/res/anim/slide_up.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/color/field_box_lable_color.xml b/app/src/main/res/color/field_box_lable_color.xml
new file mode 100644
index 00000000..61445632
--- /dev/null
+++ b/app/src/main/res/color/field_box_lable_color.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/field_box_lable_error_color.xml b/app/src/main/res/color/field_box_lable_error_color.xml
new file mode 100644
index 00000000..fbd47533
--- /dev/null
+++ b/app/src/main/res/color/field_box_lable_error_color.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/field_box_with_lable_error_stroke_color.xml b/app/src/main/res/color/field_box_with_lable_error_stroke_color.xml
new file mode 100644
index 00000000..fbd47533
--- /dev/null
+++ b/app/src/main/res/color/field_box_with_lable_error_stroke_color.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/field_box_with_lable_stroke_color.xml b/app/src/main/res/color/field_box_with_lable_stroke_color.xml
new file mode 100644
index 00000000..632a5807
--- /dev/null
+++ b/app/src/main/res/color/field_box_with_lable_stroke_color.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/background_textbox.xml b/app/src/main/res/drawable-v24/background_textbox.xml
new file mode 100644
index 00000000..6e690156
--- /dev/null
+++ b/app/src/main/res/drawable-v24/background_textbox.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v24/background_textbox_activated.xml b/app/src/main/res/drawable-v24/background_textbox_activated.xml
new file mode 100644
index 00000000..cb863a36
--- /dev/null
+++ b/app/src/main/res/drawable-v24/background_textbox_activated.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v24/selector_number_box.xml b/app/src/main/res/drawable-v24/selector_number_box.xml
new file mode 100644
index 00000000..375e6231
--- /dev/null
+++ b/app/src/main/res/drawable-v24/selector_number_box.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/shape_small_radius_background.xml b/app/src/main/res/drawable-v24/shape_small_radius_background.xml
new file mode 100644
index 00000000..f53e2d92
--- /dev/null
+++ b/app/src/main/res/drawable-v24/shape_small_radius_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_chips.xml b/app/src/main/res/drawable/background_chips.xml
new file mode 100644
index 00000000..8ebebd74
--- /dev/null
+++ b/app/src/main/res/drawable/background_chips.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_searchbar.xml b/app/src/main/res/drawable/background_searchbar.xml
new file mode 100644
index 00000000..ef8a168e
--- /dev/null
+++ b/app/src/main/res/drawable/background_searchbar.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bottom_sheet_dialog_shape.xml b/app/src/main/res/drawable/bottom_sheet_dialog_shape.xml
new file mode 100644
index 00000000..d82c7715
--- /dev/null
+++ b/app/src/main/res/drawable/bottom_sheet_dialog_shape.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/button_background.xml b/app/src/main/res/drawable/button_background.xml
new file mode 100644
index 00000000..b76bf45b
--- /dev/null
+++ b/app/src/main/res/drawable/button_background.xml
@@ -0,0 +1,14 @@
+
+
+-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/cover_gradient_background.xml b/app/src/main/res/drawable/cover_gradient_background.xml
new file mode 100644
index 00000000..fc43dfb8
--- /dev/null
+++ b/app/src/main/res/drawable/cover_gradient_background.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/gray_background.xml b/app/src/main/res/drawable/gray_background.xml
new file mode 100644
index 00000000..d8a9d54a
--- /dev/null
+++ b/app/src/main/res/drawable/gray_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_back_button.xml b/app/src/main/res/drawable/ic_back_button.xml
new file mode 100644
index 00000000..01b2c76e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_back_button.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml
new file mode 100644
index 00000000..95cd4cb6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bookmark.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 00000000..996e16d0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000..4ea8ca17
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 00000000..54021082
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/input_text_background.xml b/app/src/main/res/drawable/input_text_background.xml
new file mode 100644
index 00000000..2dc47bcb
--- /dev/null
+++ b/app/src/main/res/drawable/input_text_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/roboto_bold.ttf b/app/src/main/res/font/roboto_bold.ttf
new file mode 100644
index 00000000..68822caf
Binary files /dev/null and b/app/src/main/res/font/roboto_bold.ttf differ
diff --git a/app/src/main/res/font/roboto_font_family.xml b/app/src/main/res/font/roboto_font_family.xml
new file mode 100644
index 00000000..04941c50
--- /dev/null
+++ b/app/src/main/res/font/roboto_font_family.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/roboto_light.ttf b/app/src/main/res/font/roboto_light.ttf
new file mode 100644
index 00000000..aa453407
Binary files /dev/null and b/app/src/main/res/font/roboto_light.ttf differ
diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf
new file mode 100644
index 00000000..a3c1a1f1
Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ
diff --git a/app/src/main/res/font/roboto_regular.ttf b/app/src/main/res/font/roboto_regular.ttf
new file mode 100644
index 00000000..0e58508a
Binary files /dev/null and b/app/src/main/res/font/roboto_regular.ttf differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..879fd5ba
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_no_internet.xml b/app/src/main/res/layout/activity_no_internet.xml
new file mode 100644
index 00000000..77d9ef65
--- /dev/null
+++ b/app/src/main/res/layout/activity_no_internet.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
new file mode 100644
index 00000000..eb47b3ba
--- /dev/null
+++ b/app/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/bottom_sheet_reviews.xml b/app/src/main/res/layout/bottom_sheet_reviews.xml
new file mode 100644
index 00000000..cdd31dc7
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_reviews.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_movie_detail.xml b/app/src/main/res/layout/fragment_movie_detail.xml
new file mode 100644
index 00000000..9f292002
--- /dev/null
+++ b/app/src/main/res/layout/fragment_movie_detail.xml
@@ -0,0 +1,572 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_movies.xml b/app/src/main/res/layout/fragment_movies.xml
new file mode 100644
index 00000000..5fdf2a95
--- /dev/null
+++ b/app/src/main/res/layout/fragment_movies.xml
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 00000000..c0368f8f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_cast.xml b/app/src/main/res/layout/item_cast.xml
new file mode 100644
index 00000000..4008f89e
--- /dev/null
+++ b/app/src/main/res/layout/item_cast.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_genre.xml b/app/src/main/res/layout/item_genre.xml
new file mode 100644
index 00000000..0ae321ce
--- /dev/null
+++ b/app/src/main/res/layout/item_genre.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_movie.xml b/app/src/main/res/layout/item_movie.xml
new file mode 100644
index 00000000..063c0914
--- /dev/null
+++ b/app/src/main/res/layout/item_movie.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_poster.xml b/app/src/main/res/layout/item_poster.xml
new file mode 100644
index 00000000..84d22bf6
--- /dev/null
+++ b/app/src/main/res/layout/item_poster.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_review.xml b/app/src/main/res/layout/item_review.xml
new file mode 100644
index 00000000..d8fbd15c
--- /dev/null
+++ b/app/src/main/res/layout/item_review.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..7353dbd1
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..7353dbd1
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..69859eb9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..5c318e6f
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..5730d151
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..5e7a9819
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-nodpi/arash_image.jpg b/app/src/main/res/mipmap-nodpi/arash_image.jpg
new file mode 100644
index 00000000..703553b3
Binary files /dev/null and b/app/src/main/res/mipmap-nodpi/arash_image.jpg differ
diff --git a/app/src/main/res/mipmap-nodpi/avatar_place_holder.png b/app/src/main/res/mipmap-nodpi/avatar_place_holder.png
new file mode 100644
index 00000000..4667a3ef
Binary files /dev/null and b/app/src/main/res/mipmap-nodpi/avatar_place_holder.png differ
diff --git a/app/src/main/res/mipmap-nodpi/daresay_logo.png b/app/src/main/res/mipmap-nodpi/daresay_logo.png
new file mode 100644
index 00000000..ceb3504d
Binary files /dev/null and b/app/src/main/res/mipmap-nodpi/daresay_logo.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..58941eb6
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..e74b109c
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..db882934
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..2c05b0d3
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..bdd39178
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..c724ac0e
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml
new file mode 100644
index 00000000..2cd5ab68
--- /dev/null
+++ b/app/src/main/res/navigation/main_navigation.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/loading.json b/app/src/main/res/raw/loading.json
new file mode 100644
index 00000000..8d6484d0
--- /dev/null
+++ b/app/src/main/res/raw/loading.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":30,"ip":0,"op":66,"w":540,"h":540,"nm":"Loading_2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Ball_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[270,270,0],"ix":2,"l":2},"a":{"a":0,"k":[-126.25,88.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":5,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":17,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":59,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":61,"s":[80,80,100]},{"t":63,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.5,45.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elÃptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-126.25,88.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[85.703,85.703],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Ball_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[270,270,0],"to":[-7.562,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":40,"s":[224.625,270,0],"to":[0,0,0],"ti":[-7.562,0,0]},{"t":66,"s":[270,270,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-126.25,88.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.5,45.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elÃptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-126.25,88.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[50.052,50.052],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Ball_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[270,270,0],"to":[7.375,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":30,"s":[314.25,270,0],"to":[0,0,0],"ti":[7.375,0,0]},{"t":66,"s":[270,270,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-126.25,88.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.5,45.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elÃptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-126.25,88.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[50.052,50.052],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Ball_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":15,"s":[270,270,0],"to":[-7.562,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":45,"s":[224.625,270,0],"to":[0,0,0],"ti":[-7.562,0,0]},{"t":66,"s":[270,270,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-126.25,88.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.5,45.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elÃptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-126.25,88.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[50.052,50.052],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Ball_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":5,"s":[270,270,0],"to":[7.375,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":35,"s":[314.25,270,0],"to":[0,0,0],"ti":[7.375,0,0]},{"t":66,"s":[270,270,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-126.25,88.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[45.5,45.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elÃptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.6941176470588235,0.12549019607843137,0.1450980392156863,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-126.25,88.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[50.052,50.052],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..8ac32bec
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,249 @@
+
+
+ #292929
+ #E61E25
+ #B12025
+ #FF000000
+ #FFFFFFFF
+ #00FFFFFF
+ #270077
+ #FF018786
+ #FFBB86FC
+ #B721FF
+
+ #0CE7DC
+ #6FF3EC
+ #AAFBF7
+ #EAFEFD
+ #F5FEFE
+ #00948A
+
+
+ #4A6C90
+ #7393B7
+ #A7BCD2
+ #EDF2F6
+ #F6F8FA
+ #D3DDE8
+ #031E32
+
+
+ #FF7F87
+ #FFA2A8
+ #FFC5C9
+ #FFF3F4
+ #FFFCFC
+ #C6243D
+
+
+ #FFE2A4
+ #F4C9DE
+ #CBECF5
+ #BDBFEF
+ #CDEDDA
+
+
+ #373737
+ #636363
+ #8D8D8D
+ #B8B8B8
+ #D4D4D4
+ #F1F1F1
+
+
+ #00C559
+ #FFCA0F
+ #179EF8
+
+
+ @color/green
+ @color/yellow
+ @color/primaryRedDark
+ @color/grayE
+ @color/blue
+
+
+ @color/white
+ @color/primaryBlueF
+ @color/primaryBlueE
+
+
+ @color/grayC
+ @color/primaryBlueE
+ @color/white
+ @color/primaryBlueB
+ @color/white
+
+ @color/primaryBlueB
+ @color/white
+ @color/grayB
+ @color/grayD
+ @color/primaryBlueC
+
+ @color/white
+ @color/primaryBlueF
+
+ @color/grayE
+ @color/primaryBlueC
+ @color/primaryBlueE
+
+ @color/grayD
+ @color/grayB
+ @color/grayD
+ @color/primaryRedE
+ @color/grayA
+ @color/primaryGreenF
+
+ @color/white
+
+ @color/grayA
+ @color/grayB
+ @color/white
+ @color/primaryBlueC
+
+ @color/primaryGreenB
+ @color/white
+
+ @color/grayB
+ @color/grayC
+ @color/white
+ @color/primaryBlueC
+
+ @color/grayB
+ @color/grayC
+ @color/primaryBlueD
+ @color/white
+ @color/primaryBlueB
+
+ @color/grayB
+ @color/grayC
+ @color/white
+
+ @color/grayC
+ @color/grayD
+ @color/grayE
+ @color/primaryBlueD
+ @color/primaryBlueE
+ @color/primaryBlueF
+ @color/white
+
+ @color/primaryGreenDark
+ @color/grayC
+ @color/grayD
+ @color/primaryBlueB
+ @color/primaryBlueD
+ @color/primaryBlueE
+ @color/primaryBlueF
+ @color/white
+
+ @color/primaryBlueB
+
+ @color/grayE
+ @color/grayF
+
+ @color/grayE
+ @color/white
+
+ @color/primaryBlueB
+ @color/primaryBlueB
+
+ @color/primaryRedDark
+ @color/primaryRedDark
+ @color/primaryBlueDark
+ @color/primaryBlueDark
+ @color/primaryGreenDark
+
+ @color/primaryGreenDark
+ @color/primaryRedDark
+ @color/white
+
+ @color/grayA
+ @color/pastelBlue
+ @color/primaryRedE
+ @color/white
+
+ @color/primaryBlueB
+ @color/primaryBlueE
+ @color/white
+
+ @color/primaryBlueB
+ @color/primaryBlueD
+ @color/white
+
+ @color/white
+ @color/primaryBlueC
+
+ @color/primaryBlueG
+ @color/pastelGreen
+ @color/pastelPink
+
+
+ @color/backgroundBGB
+ @color/grayE
+
+ @color/grayF
+
+ #1AC6243D
+ #1A00C559
+
+ #CCE7EEF8
+ #bedcf0
+ #66bedcf0
+ #bedcf0
+ #e0e0e0
+ #e0e0e0
+ #d3d3d3
+ #ffffff
+ #f5f5f5
+ #000000
+ #E5E5E5
+ #ffffff
+ #d3d3d3
+
+ #f5f5f5
+
+ #666666
+ #212121
+ #9E9E9E
+ #c1c1c1
+
+ #ffffff
+ #9E9E9E
+ #16AFF4
+ #54C4F7
+ #3FBDF6
+ #E0E0E0
+
+ #ffffff
+ #9E9E9E
+ #16AFF4
+ #54C4F7
+ #3FBDF6
+ #E0E0E0
+
+ #D3D3D3
+ #E0E0E0
+ #FFFFFF
+ #EBF8FF
+ #C2EAFD
+ #ADE3FC
+ #F5F5F5
+
+ #E0E0E0
+ #FFFFFF
+ #F5FCFF
+ #E0F5FE
+ #E0F5FE
+ #F5F5F5
+
+ #f75038
+ #53CCF7
+ #017DD8
+
+ #29D6F1FE
+ #14D6F1FE
+ #CCFFFFFF
+
+ #78000000
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..9ab565db
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,167 @@
+
+
+ 34dp
+ 22dp
+ 16dp
+ 14dp
+ 14dp
+ 12dp
+ 10dp
+ 8dp
+ 14dp
+ 12dp
+ 56dp
+ 48dp
+ 296dp
+ 140dp
+ 24dp
+ 32dp
+ 48dp
+ 8dp
+ 12dp
+ 24dp
+ 4dp
+ 4dp
+ 0dp
+ 1dp
+ 32dp
+ 24dp
+ 16dp
+ 40dp
+ 62dp
+ 32dp
+ 5dp
+ 8dp
+ 12dp
+ 16dp
+ 20dp
+ 24dp
+ 28dp
+ 32dp
+ 36dp
+ 40dp
+ 0dp
+ 1dp
+ 2dp
+ 3dp
+ 4dp
+ 6dp
+ 8dp
+ 10dp
+ 12dp
+ 14dp
+ 16dp
+ 18dp
+ 20dp
+ 24dp
+ 32dp
+ 36dp
+ 48dp
+ 58dp
+ 68dp
+ 48dp
+ 40dp
+ 32dp
+ 64dp
+ 62dp
+ 40dp
+
+ 72dp
+ 40dp
+ 56dp
+ 44dp
+ 28dp
+ 10dp
+
+ 400dp
+
+ 20dp
+ 56dp
+ 14dp
+ 20dp
+ 14dp
+ 14dp
+ 14dp
+ 2dp
+ 1dp
+ 8dp
+ 8dp
+ 16dp
+ 8dp
+ 8dp
+ 0.08
+ 0.92
+ 0.04
+ 0.96
+ 0.08
+ 50dp
+ 30dp
+ 0.35
+ 0.16
+ .60
+ 50dp
+ 56dp
+ 10dp
+ 16dp
+ 16dp
+ 16dp
+ 30dp
+ 36dp
+ 48dp
+ 40dp
+ 8dp
+ 10dp
+ 21sp
+ 12dp
+ 10dp
+ 8dp
+ 48dp
+ 97dp
+ 32dp
+ 11dp
+ 16dp
+ 32dp
+ 44dp
+ 32dp
+ 12dp
+ 10dp
+ 4dp
+ 10dp
+ 14dp
+ 12dp
+ 32dp
+ 16dp
+ 12dp
+ 8dp
+ 30dp
+ 28dp
+ 28dp
+ 32dp
+ 28dp
+ 296dp
+ 138dp
+ 38dp
+ 18dp
+ 5dp
+ 24dp
+ 48dp
+ 36dp
+ 14dp
+ 12dp
+ 68dp
+ 4dp
+ 4dp
+ 8dp
+ 16dp
+ 36dp
+ 36dp
+ 48dp
+ 48dp
+ 48dp
+ 56dp
+ 40dp
+ 58dp
+ 48dp
+ 72dp
+ 48dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 00000000..beab31f7
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #000000
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..e083888c
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+ Daresay Movie
+ Something went wrong, please try again later
+ Oops! seems we have a problem, please try again in a moment!
+ it seems there\'s some issues came up, please try again in a moment
+ What\'s Popular
+ Now Playing
+ Upcoming
+ Top Rated
+ Search
+ Favorite Movies
+ Watchlist
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..d395b25d
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,351 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //Number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //BigTitle
+
+
+
+
+
+
+
+
+
+
+
+ //Heading1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //Heading2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //Heading 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/typography.xml b/app/src/main/res/values/typography.xml
new file mode 100644
index 00000000..ca76b548
--- /dev/null
+++ b/app/src/main/res/values/typography.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/arashafsharpour/daresaymovie/ExampleUnitTest.kt b/app/src/test/java/com/arashafsharpour/daresaymovie/ExampleUnitTest.kt
new file mode 100644
index 00000000..ce207636
--- /dev/null
+++ b/app/src/test/java/com/arashafsharpour/daresaymovie/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.arashafsharpour.daresaymovie
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..ced09d1d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ ext.java_version = JavaVersion.VERSION_1_8
+ ext.gradle_version = '7.0.3'
+ ext.kotlin_version = "1.5.30"
+ ext.hilt_version = "2.40.1"
+
+
+ repositories {
+ google()
+ mavenCentral()
+ maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
+ }
+ dependencies {
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
+ classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
+ classpath 'com.android.tools.build:gradle:7.0.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0"
+ classpath "com.android.tools.build:gradle:$gradle_version"
+ classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
+ classpath "com.github.ben-manes:gradle-versions-plugin:0.11.1"
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml
new file mode 100644
index 00000000..fffca60d
--- /dev/null
+++ b/config/detekt/detekt.yml
@@ -0,0 +1,737 @@
+build:
+ maxIssues: 0
+ excludeCorrectable: false
+ weights:
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ warningsAsErrors: false
+ # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ''
+
+processors:
+ active: true
+ exclude:
+ - 'DetektProgressListener'
+ # - 'KtFileCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'ClassCountProcessor'
+ # - 'FunctionCountProcessor'
+ # - 'PropertyCountProcessor'
+ # - 'ProjectComplexityProcessor'
+ # - 'ProjectCognitiveComplexityProcessor'
+ # - 'ProjectLLOCProcessor'
+ # - 'ProjectCLOCProcessor'
+ # - 'ProjectLOCProcessor'
+ # - 'ProjectSLOCProcessor'
+ # - 'LicenseHeaderLoaderExtension'
+
+console-reports:
+ active: true
+ exclude:
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ # - 'FindingsReport'
+ - 'FileBasedFindingsReport'
+
+output-reports:
+ active: true
+ exclude:
+ # - 'TxtOutputReport'
+ # - 'XmlOutputReport'
+ # - 'HtmlOutputReport'
+
+comments:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateFile: 'license.template'
+ licenseTemplateIsRegex: false
+ CommentOverPrivateFunction:
+ active: false
+ CommentOverPrivateProperty:
+ active: false
+ EndOfSentenceFormat:
+ active: false
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ UndocumentedPublicClass:
+ active: false
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ UndocumentedPublicFunction:
+ active: false
+ UndocumentedPublicProperty:
+ active: false
+
+complexity:
+ active: true
+ ComplexCondition:
+ active: true
+ threshold: 5
+ ComplexInterface:
+ active: false
+ threshold: 10
+ includeStaticDeclarations: false
+ includePrivateDeclarations: false
+ ComplexMethod:
+ active: true
+ threshold: 17
+ ignoreSingleWhenExpression: false
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions: [ run, let, apply, with, also, use, forEach, isNotNull, ifNull ]
+ LabeledExpression:
+ active: false
+ ignoredLabels: [ ]
+ LargeClass:
+ active: true
+ threshold: 600
+ LongMethod:
+ active: true
+ threshold: 60
+ LongParameterList:
+ active: true
+ functionThreshold: 9
+ constructorThreshold: 9
+ ignoreDefaultParameters: false
+ ignoreDataClasses: true
+ ignoreAnnotated: [ ]
+ MethodOverloading:
+ active: false
+ threshold: 6
+ NamedArguments:
+ active: false
+ threshold: 3
+ NestedBlockDepth:
+ active: true
+ threshold: 5
+ ReplaceSafeCallChainWithRun:
+ active: false
+ StringLiteralDuplication:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ threshold: 3
+ ignoreAnnotation: true
+ excludeStringsWithLessThan5Characters: true
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ thresholdInFiles: 15
+ thresholdInClasses: 24
+ thresholdInInterfaces: 20
+ thresholdInObjects: 11
+ thresholdInEnums: 11
+ ignoreDeprecated: false
+ ignorePrivate: false
+ ignoreOverridden: false
+
+coroutines:
+ active: true
+ GlobalCoroutineUsage:
+ active: false
+ RedundantSuspendModifier:
+ active: false
+ SleepInsteadOfDelay:
+ active: false
+ SuspendFunWithFlowReturnType:
+ active: false
+
+empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverridden: true
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKtFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyTryBlock:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+
+exceptions:
+ active: true
+ ExceptionRaisedInUnexpectedLocation:
+ active: true
+ methodNames: [ toString, hashCode, equals, finalize ]
+ InstanceOfCheckForException:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ NotImplementedDeclaration:
+ active: false
+ ObjectExtendsThrowable:
+ active: false
+ PrintStackTrace:
+ active: true
+ RethrowCaughtException:
+ active: true
+ ReturnFromFinally:
+ active: true
+ ignoreLabeled: false
+ SwallowedException:
+ active: true
+ ignoredExceptionTypes:
+ - InterruptedException
+ - NumberFormatException
+ - ParseException
+ - MalformedURLException
+ - Exception
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ ThrowingExceptionFromFinally:
+ active: true
+ ThrowingExceptionInMain:
+ active: false
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ exceptions:
+ - IllegalArgumentException
+ - IllegalStateException
+ - IOException
+ ThrowingNewInstanceOfSameException:
+ active: true
+ TooGenericExceptionCaught:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ exceptionNames:
+ - ArrayIndexOutOfBoundsException
+ - Error
+ - Exception
+ - IllegalMonitorStateException
+ - NullPointerException
+ - IndexOutOfBoundsException
+ - RuntimeException
+ - Throwable
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ TooGenericExceptionThrown:
+ active: true
+ exceptionNames:
+ - Error
+ - Exception
+ - Throwable
+ - RuntimeException
+
+formatting:
+ active: true
+ android: false
+ autoCorrect: true
+ AnnotationOnSeparateLine:
+ active: false
+ autoCorrect: true
+ AnnotationSpacing:
+ active: false
+ autoCorrect: true
+ ArgumentListWrapping:
+ active: false
+ autoCorrect: true
+ ChainWrapping:
+ active: true
+ autoCorrect: true
+ CommentSpacing:
+ active: true
+ autoCorrect: true
+ EnumEntryNameCase:
+ active: false
+ autoCorrect: true
+ Filename:
+ active: true
+ FinalNewline:
+ active: true
+ autoCorrect: true
+ insertFinalNewLine: true
+ ImportOrdering:
+ active: false
+ autoCorrect: true
+ layout: 'idea'
+ Indentation:
+ active: false
+ autoCorrect: true
+ indentSize: 4
+ continuationIndentSize: 4
+ MaximumLineLength:
+ active: true
+ maxLineLength: 120
+ ModifierOrdering:
+ active: true
+ autoCorrect: true
+ MultiLineIfElse:
+ active: true
+ autoCorrect: true
+ NoBlankLineBeforeRbrace:
+ active: true
+ autoCorrect: true
+ NoConsecutiveBlankLines:
+ active: true
+ autoCorrect: true
+ NoEmptyClassBody:
+ active: true
+ autoCorrect: true
+ NoEmptyFirstLineInMethodBlock:
+ active: false
+ autoCorrect: true
+ NoLineBreakAfterElse:
+ active: true
+ autoCorrect: true
+ NoLineBreakBeforeAssignment:
+ active: true
+ autoCorrect: true
+ NoMultipleSpaces:
+ active: true
+ autoCorrect: true
+ NoSemicolons:
+ active: true
+ autoCorrect: true
+ NoTrailingSpaces:
+ active: true
+ autoCorrect: true
+ NoUnitReturn:
+ active: true
+ autoCorrect: true
+ NoUnusedImports:
+ active: true
+ autoCorrect: true
+ NoWildcardImports:
+ active: false
+ PackageName:
+ active: true
+ autoCorrect: true
+ ParameterListWrapping:
+ active: true
+ autoCorrect: true
+ indentSize: 4
+ SpacingAroundAngleBrackets:
+ active: false
+ autoCorrect: true
+ SpacingAroundColon:
+ active: true
+ autoCorrect: true
+ SpacingAroundComma:
+ active: true
+ autoCorrect: true
+ SpacingAroundCurly:
+ active: true
+ autoCorrect: true
+ SpacingAroundDot:
+ active: true
+ autoCorrect: true
+ SpacingAroundDoubleColon:
+ active: false
+ autoCorrect: true
+ SpacingAroundKeyword:
+ active: true
+ autoCorrect: true
+ SpacingAroundOperators:
+ active: true
+ autoCorrect: true
+ SpacingAroundParens:
+ active: true
+ autoCorrect: true
+ SpacingAroundRangeOperator:
+ active: true
+ autoCorrect: true
+ SpacingAroundUnaryOperator:
+ active: false
+ autoCorrect: true
+ SpacingBetweenDeclarationsWithAnnotations:
+ active: false
+ autoCorrect: true
+ SpacingBetweenDeclarationsWithComments:
+ active: false
+ autoCorrect: true
+ StringTemplate:
+ active: true
+ autoCorrect: true
+
+naming:
+ active: true
+ ClassNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ classPattern: '[A-Z][a-zA-Z0-9]*'
+ ConstructorParameterNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ EnumNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ forbiddenName: [ ]
+ FunctionMaxLength:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ maximumFunctionNameLength: 30
+ FunctionMinLength:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ minimumFunctionNameLength: 3
+ FunctionNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ ignoreAnnotated: [ 'Composable' ]
+ FunctionParameterNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ InvalidPackageDeclaration:
+ active: false
+ excludes: [ '*.kts' ]
+ rootPackage: ''
+ MatchingDeclarationName:
+ active: true
+ mustBeFirst: true
+ MemberNameEqualsClassName:
+ active: true
+ ignoreOverridden: true
+ NoNameShadowing:
+ active: false
+ NonBooleanPropertyPrefixedWithIs:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ ObjectPropertyNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
+ TopLevelPropertyNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ ForEachOnRange:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ SpreadOperator:
+ active: true
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ UnnecessaryTemporaryInstantiation:
+ active: true
+
+potential-bugs:
+ active: true
+ CastToNullableType:
+ active: false
+ Deprecation:
+ active: false
+ DontDowncastCollectionTypes:
+ active: false
+ DuplicateCaseInWhenExpression:
+ active: true
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExitOutsideMain:
+ active: false
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: false
+ IgnoredReturnValue:
+ active: false
+ restrictToAnnotatedMethods: true
+ returnValueAnnotations: [ '*.CheckReturnValue', '*.CheckResult' ]
+ ImplicitDefaultLocale:
+ active: true
+ ImplicitUnitReturnType:
+ active: false
+ allowExplicitReturnType: true
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ excludeAnnotatedProperties: [ ]
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: false
+ MissingWhenCase:
+ active: true
+ allowElseExpression: true
+ NullableToStringCall:
+ active: false
+ RedundantElseInWhen:
+ active: true
+ UnconditionalJumpStatementInLoop:
+ active: false
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: false
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ UnsafeCast:
+ active: true
+ UnusedUnaryOperator:
+ active: false
+ UselessPostfixExpression:
+ active: false
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ ClassOrdering:
+ active: false
+ CollapsibleIfStatements:
+ active: false
+ DataClassContainsFunctions:
+ active: false
+ conversionFunctionPrefix: 'to'
+ DataClassShouldBeImmutable:
+ active: false
+ DestructuringDeclarationWithTooManyEntries:
+ active: false
+ maxDestructuringEntries: 3
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: false
+ ExplicitCollectionElementAccessMethod:
+ active: false
+ ExplicitItLambdaParameter:
+ active: false
+ ExpressionBodySyntax:
+ active: false
+ includeLineWrapping: false
+ ForbiddenComment:
+ active: true
+ values: [ 'TODO:', 'FIXME:', 'STOPSHIP:' ]
+ allowedPatterns: ''
+ ForbiddenImport:
+ active: false
+ imports: [ ]
+ forbiddenPatterns: ''
+ ForbiddenMethodCall:
+ active: false
+ methods: [ 'kotlin.io.println', 'kotlin.io.print' ]
+ ForbiddenPublicDataClass:
+ active: true
+ excludes: [ '**' ]
+ ignorePackages: [ '*.internal', '*.internal.*' ]
+ ForbiddenVoid:
+ active: false
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ ignoreActualFunction: true
+ excludedFunctions: 'describeContents'
+ excludeAnnotatedFunction: [ 'dagger.Provides' ]
+ LibraryCodeMustSpecifyReturnType:
+ active: true
+ excludes: [ '**' ]
+ LibraryEntitiesShouldNotBePublic:
+ active: true
+ excludes: [ '**' ]
+ LoopWithTooManyJumpStatements:
+ active: true
+ maxJumpCount: 1
+ MagicNumber:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**' ]
+ ignoreNumbers: [ '-1', '0', '1', '2','1000' ]
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: false
+ ignoreLocalVariableDeclaration: false
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ ignoreExtensionFunctions: true
+ MandatoryBracesIfStatements:
+ active: false
+ MandatoryBracesLoops:
+ active: false
+ MaxLineLength:
+ active: true
+ maxLineLength: 120
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ MayBeConst:
+ active: true
+ ModifierOrder:
+ active: true
+ MultilineLambdaItParameter:
+ active: false
+ NestedClassesVisibility:
+ active: true
+ NewLineAtEndOfFile:
+ active: false
+ NoTabs:
+ active: false
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false
+ OptionalWhenBraces:
+ active: false
+ PreferToOverPairSyntax:
+ active: false
+ ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
+ active: false
+ RedundantHigherOrderMapUsage:
+ active: false
+ RedundantVisibilityModifierRule:
+ active: false
+ ReturnCount:
+ active: true
+ max: 3
+ excludedFunctions: 'equals'
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: true
+ SpacingBetweenPackageAndImports:
+ active: false
+ ThrowsCount:
+ active: true
+ max: 2
+ TrailingWhitespace:
+ active: false
+ UnderscoresInNumericLiterals:
+ active: false
+ acceptableDecimalLength: 5
+ UnnecessaryAbstractClass:
+ active: true
+ excludeAnnotatedClasses: [ 'dagger.Module' ]
+ UnnecessaryAnnotationUseSiteTarget:
+ active: false
+ UnnecessaryApply:
+ active: true
+ UnnecessaryFilter:
+ active: false
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryLet:
+ active: false
+ UnnecessaryParentheses:
+ active: false
+ UntilInsteadOfRangeTo:
+ active: false
+ UnusedImports:
+ active: false
+ UnusedPrivateClass:
+ active: true
+ UnusedPrivateMember:
+ active: true
+ allowedNames: '(_|ignored|expected|serialVersionUID)'
+ UseArrayLiteralsInAnnotations:
+ active: false
+ UseCheckNotNull:
+ active: false
+ UseCheckOrError:
+ active: false
+ UseDataClass:
+ active: false
+ excludeAnnotatedClasses: [ ]
+ allowVars: false
+ UseEmptyCounterpart:
+ active: false
+ UseIfEmptyOrIfBlank:
+ active: false
+ UseIfInsteadOfWhen:
+ active: false
+ UseIsNullOrEmpty:
+ active: false
+ UseOrEmpty:
+ active: false
+ UseRequire:
+ active: false
+ UseRequireNotNull:
+ active: false
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: true
+ WildcardImport:
+ active: false
+ excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ]
+ excludeImports: [ 'java.util.*', 'kotlinx.android.synthetic.*' ]
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..a9d5d0bc
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+android.useAndroidX=true
+android.enableJetifier=true
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..d1944d88
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 06 22:39:26 IRST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 00000000..4f906e0c
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..107acd32
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/local.properties b/local.properties
new file mode 100644
index 00000000..08f7c9fa
--- /dev/null
+++ b/local.properties
@@ -0,0 +1,8 @@
+## This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Wed Nov 24 21:22:24 IRST 2021
+sdk.dir=/Users/arashafshar/Library/Android/sdk
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..787638f0
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,9 @@
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "DaresayMovie"
+include ':app'