├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── empty.png
│ │ │ │ ├── blank_profile_image.png
│ │ │ │ ├── no_image_available.jpg
│ │ │ │ ├── auth_background_image.jpeg
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── splash.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-night
│ │ │ │ └── splash.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── layout
│ │ │ │ └── youtube_trailer_item.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── movieapp
│ │ │ │ ├── MovieApplication.kt
│ │ │ │ └── di
│ │ │ │ └── AppModule.kt
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ahmetocak
│ │ └── movieapp
│ │ └── ExampleInstrumentedTest.kt
└── proguard-rules.pro
├── core
├── common
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── drawable
│ │ │ │ │ └── empty.png
│ │ │ │ ├── values-tr-rTR
│ │ │ │ │ └── strings.xml
│ │ │ │ └── values
│ │ │ │ │ └── strings.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── ahmetocak
│ │ │ │ │ └── common
│ │ │ │ │ ├── constants
│ │ │ │ │ ├── Constants.kt
│ │ │ │ │ └── SeeAllType.kt
│ │ │ │ │ ├── helpers
│ │ │ │ │ ├── DialogUiEvent.kt
│ │ │ │ │ ├── ScreenOrientation.kt
│ │ │ │ │ ├── ConditionalModifier.kt
│ │ │ │ │ ├── Response.kt
│ │ │ │ │ ├── UiState.kt
│ │ │ │ │ ├── ShowUserMessage.kt
│ │ │ │ │ ├── HandleTaskError.kt
│ │ │ │ │ └── UiText.kt
│ │ │ │ │ ├── connectivity
│ │ │ │ │ └── ConnectivityObserver.kt
│ │ │ │ │ ├── utils
│ │ │ │ │ └── ResponseMapper.kt
│ │ │ │ │ └── Extensions.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── common
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── common
│ │ │ └── ExampleInstrumentedTest.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── data
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── data
│ │ │ ├── repository
│ │ │ ├── datastore
│ │ │ │ ├── DataStoreRepository.kt
│ │ │ │ └── DataStoreRepositoryImpl.kt
│ │ │ └── firebase
│ │ │ │ ├── FirebaseRepository.kt
│ │ │ │ └── FirebaseRepositoryImpl.kt
│ │ │ └── mapper
│ │ │ └── FirebaseMapper.kt
│ └── build.gradle.kts
├── domain
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── domain
│ │ │ ├── movie
│ │ │ ├── SearchMovieUseCase.kt
│ │ │ ├── GetAllTopRatedMoviesUseCase.kt
│ │ │ ├── GetAllTrendingMoviesUseCase.kt
│ │ │ ├── GetAllUpcomingMoviesUseCase.kt
│ │ │ ├── GetActorMoviesUseCase.kt
│ │ │ ├── ObserveWatchListUseCase.kt
│ │ │ ├── GetActorDetailsUseCase.kt
│ │ │ ├── GetMovieCreditsUseCase.kt
│ │ │ ├── GetMovieDetailsUseCase.kt
│ │ │ ├── GetMovieTrailersUseCase.kt
│ │ │ ├── GetTrendingMoviesFirstPageUseCase.kt
│ │ │ ├── GetUserMovieReviewsUseCase.kt
│ │ │ ├── GetMovieRecommendationsUseCase.kt
│ │ │ ├── GetTopRatedMoviesFirstPageUseCase.kt
│ │ │ ├── GetUpcomingMoviesFirstPageUseCase.kt
│ │ │ ├── RemoveMovieFromWatchListUseCase.kt
│ │ │ └── AddMovieToDbWatchListUseCase.kt
│ │ │ ├── firebase
│ │ │ ├── auth
│ │ │ │ ├── GetUserEmailUseCase.kt
│ │ │ │ ├── SendResetPasswordEmailUseCase.kt
│ │ │ │ ├── LoginUseCase.kt
│ │ │ │ ├── SignUpUseCase.kt
│ │ │ │ ├── SignOutUseCase.kt
│ │ │ │ ├── SignInWithGoogleUseCase.kt
│ │ │ │ └── ReAuthenticateUseCase.kt
│ │ │ ├── firestore
│ │ │ │ ├── GetMovieDataUseCase.kt
│ │ │ │ ├── UpdateMovieDataUseCase.kt
│ │ │ │ └── AddMovieToWatchListUseCase.kt
│ │ │ ├── storage
│ │ │ │ ├── GetUserProfileImageUseCase.kt
│ │ │ │ └── UploadProfileImageUseCase.kt
│ │ │ └── CheckMovieInWatchListUseCase.kt
│ │ │ ├── preferences
│ │ │ ├── ObserveAppThemeUseCase.kt
│ │ │ ├── ObserveDynamicColorUseCase.kt
│ │ │ ├── UpdateAppThemeUseCase.kt
│ │ │ └── UpdateDynamicColorUseCase.kt
│ │ │ └── GetGeminiResponseUseCase.kt
│ └── build.gradle.kts
├── model
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── model
│ │ │ ├── movie_detail
│ │ │ ├── MovieTrailer.kt
│ │ │ ├── MovieCredit.kt
│ │ │ └── MovieDetail.kt
│ │ │ ├── watch_list
│ │ │ └── WatchList.kt
│ │ │ ├── movie
│ │ │ ├── MovieRecommendations.kt
│ │ │ ├── ActorDetails.kt
│ │ │ ├── ActorMovies.kt
│ │ │ ├── Movie.kt
│ │ │ └── UserMovieReviews.kt
│ │ │ └── firebase
│ │ │ └── WatchListMovie.kt
│ └── build.gradle.kts
├── ui
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── ui
│ │ │ │ └── TmdbLogo.kt
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── ui
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── ui
│ │ │ └── ExampleInstrumentedTest.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── database
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values-tr
│ │ │ │ └── strings.xml
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── database
│ │ │ ├── db
│ │ │ └── WatchListDatabase.kt
│ │ │ ├── di
│ │ │ ├── DaoModule.kt
│ │ │ ├── DatasourceModule.kt
│ │ │ └── DatabaseModule.kt
│ │ │ ├── datasource
│ │ │ ├── WatchListLocalDataSource.kt
│ │ │ └── WatchListLocalDataSourceImpl.kt
│ │ │ ├── entity
│ │ │ └── WatchListEntity.kt
│ │ │ ├── dao
│ │ │ └── WatchListDao.kt
│ │ │ └── utils
│ │ │ └── DbCall.kt
│ └── build.gradle.kts
├── datastore
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── datastore
│ │ │ ├── DatastoreConstants.kt
│ │ │ ├── datasource
│ │ │ ├── MovieAppPreferenceDataSource.kt
│ │ │ └── MovieAppPreferenceDataSourceImpl.kt
│ │ │ └── di
│ │ │ └── DatastoreModule.kt
│ └── build.gradle.kts
├── navigation
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ └── strings.xml
│ │ │ │ └── values-tr-rTR
│ │ │ │ │ └── strings.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── navigation
│ │ │ │ └── Routes.kt
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── navigation
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── navigation
│ │ │ └── ExampleInstrumentedTest.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── network
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── network
│ │ │ ├── model
│ │ │ ├── movie_detail
│ │ │ │ ├── NetworkMovieTrailer.kt
│ │ │ │ ├── NetworkMovieCredit.kt
│ │ │ │ └── NetworkMovieDetail.kt
│ │ │ ├── firebase
│ │ │ │ └── firestore
│ │ │ │ │ └── NetworkWatchListMovie.kt
│ │ │ └── movie
│ │ │ │ ├── NetworkActorDetails.kt
│ │ │ │ ├── NetworkActorMovies.kt
│ │ │ │ ├── NetworkMovie.kt
│ │ │ │ ├── NetworkMovieRecommendations.kt
│ │ │ │ └── NetworkUserMovieReview.kt
│ │ │ ├── datasource
│ │ │ ├── firebase
│ │ │ │ ├── storage
│ │ │ │ │ ├── FirebaseStorageDataSource.kt
│ │ │ │ │ └── FirebaseStorageDataSourceImpl.kt
│ │ │ │ └── firestore
│ │ │ │ │ ├── FirebaseFirestoreDataSource.kt
│ │ │ │ │ └── FirebaseFirestoreDataSourceImpl.kt
│ │ │ └── movie
│ │ │ │ ├── MovieRemoteDataSource.kt
│ │ │ │ ├── paging_source
│ │ │ │ ├── MoviesPagingSource.kt
│ │ │ │ ├── SearchMoviesPagingSource.kt
│ │ │ │ ├── UserMovieReviewsPagingSource.kt
│ │ │ │ └── RecommendedMoviesPagingSource.kt
│ │ │ │ └── MovieRemoteDataSourceImpl.kt
│ │ │ ├── di
│ │ │ ├── ApiModule.kt
│ │ │ ├── FirebaseModule.kt
│ │ │ └── DatasourceModule.kt
│ │ │ └── helpers
│ │ │ ├── ApiCall.kt
│ │ │ └── NetworkConstants.kt
│ └── build.gradle.kts
├── authentication
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── authentication
│ │ │ ├── di
│ │ │ └── AuthModule.kt
│ │ │ └── firebase
│ │ │ └── FirebaseAuthClient.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
└── designsystem
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ ├── main
│ │ ├── res
│ │ │ └── drawable
│ │ │ │ ├── no_image_available.jpg
│ │ │ │ └── auth_background_image.jpeg
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── designsystem
│ │ │ ├── AdaptiveSizeSetter.kt
│ │ │ ├── dimens
│ │ │ ├── Dimens.kt
│ │ │ └── AdaptiveModifiers.kt
│ │ │ ├── components
│ │ │ ├── auth
│ │ │ │ └── AuthWelcomeMessage.kt
│ │ │ ├── Dialog.kt
│ │ │ ├── CircularProgressIndicator.kt
│ │ │ ├── Scaffold.kt
│ │ │ ├── navigation
│ │ │ │ ├── NavigationBar.kt
│ │ │ │ └── NavigationRail.kt
│ │ │ ├── ErrorView.kt
│ │ │ ├── TextButton.kt
│ │ │ ├── Button.kt
│ │ │ └── DynamicLayout.kt
│ │ │ ├── theme
│ │ │ └── Type.kt
│ │ │ └── WindowSize.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── designsystem
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ahmetocak
│ │ └── designsystem
│ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── feature
├── login
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── values-tr
│ │ │ │ └── strings.xml
│ │ │ │ ├── drawable
│ │ │ │ └── ic_google.xml
│ │ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── login
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── login
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── movies
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ │ └── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── movies
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── movies
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── profile
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── blank_profile_image.png
│ │ │ │ ├── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── profile
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── profile
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── search
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── res
│ │ │ │ ├── values-tr-rTR
│ │ │ │ │ └── strings.xml
│ │ │ │ └── values
│ │ │ │ │ └── strings.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── search
│ │ │ │ └── SearchViewModel.kt
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── search
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── search
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── see_all
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ └── AndroidManifest.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── see_all
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── see_all
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── signup
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── values-tr
│ │ │ │ └── strings.xml
│ │ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── signup
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── signup
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── actor_details
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── actor_details
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── actor_details
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── movie_details
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res
│ │ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ │ ├── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ │ │ ├── drawable
│ │ │ │ └── ic_google_gemini.xml
│ │ │ │ └── layout
│ │ │ │ └── youtube_trailer_item.xml
│ │ ├── test
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── ahmetocak
│ │ │ │ └── movie_details
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── movie_details
│ │ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
└── watch_list
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ └── empty.png
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── values-tr-rTR
│ │ │ │ └── strings.xml
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── ahmetocak
│ │ │ └── watch_list
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ahmetocak
│ │ └── watch_list
│ │ └── ExampleInstrumentedTest.kt
│ └── proguard-rules.pro
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle.kts
└── gradle.properties
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/model/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/ui/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/common/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/database/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/datastore/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/network/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/login/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/login/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/movies/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/movies/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/search/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/search/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/see_all/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/signup/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/signup/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/authentication/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/authentication/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/designsystem/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/designsystem/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/navigation/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/actor_details/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/movie_details/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/profile/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/see_all/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/watch_list/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/watch_list/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/actor_details/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/feature/movie_details/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/authentication/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/drawable/empty.png
--------------------------------------------------------------------------------
/feature/actor_details/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/core/common/src/main/res/drawable/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/core/common/src/main/res/drawable/empty.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/blank_profile_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/drawable/blank_profile_image.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/no_image_available.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/drawable/no_image_available.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/auth_background_image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/drawable/auth_background_image.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/feature/watch_list/src/main/res/drawable/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/feature/watch_list/src/main/res/drawable/empty.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/core/ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/no_image_available.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/core/designsystem/src/main/res/drawable/no_image_available.jpg
--------------------------------------------------------------------------------
/core/navigation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/login/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/movies/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/profile/src/main/res/drawable/blank_profile_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/feature/profile/src/main/res/drawable/blank_profile_image.png
--------------------------------------------------------------------------------
/feature/search/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/see_all/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/signup/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/movie_details/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/watch_list/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/res/drawable/auth_background_image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AhmetOcak/MovieApp/HEAD/core/designsystem/src/main/res/drawable/auth_background_image.jpeg
--------------------------------------------------------------------------------
/core/database/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lütfen daha sonra tekrar deneyin
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/constants/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.constants
2 |
3 | object TMDB {
4 | const val IMAGE_URL = "https://image.tmdb.org/t/p/w500"
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle/
3 | /local.properties
4 | .idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | /*/build/
12 | /app/google-services.json
--------------------------------------------------------------------------------
/core/database/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Please try again later
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ahmetocak/movieapp/MovieApplication.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movieapp
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class MovieApplication : Application()
--------------------------------------------------------------------------------
/core/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/feature/movies/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | See All
4 | Something went wrong. Please try again later.
5 |
--------------------------------------------------------------------------------
/feature/movies/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hepsini Gör
4 | Bir şeyler ters gitti. Lütfen daha sonra tekrar deneyin.
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jan 17 12:18:42 TRT 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/core/network/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Please try again later
6 | Please check your internet connection
7 |
--------------------------------------------------------------------------------
/core/datastore/src/main/java/com/ahmetocak/datastore/DatastoreConstants.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.datastore
2 |
3 | object DataStoreConstants {
4 | const val FILE_NAME = "movie_app_preferences"
5 | const val APP_THEME_KEY = "app_theme_preference"
6 | const val DYNAMIC_COLOR_KEY = "dynamic_color_preference"
7 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/authentication/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 599679886388-78mh24j7n1l6c9jqbj96m2v4fr3qvdt7.apps.googleusercontent.com
4 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/constants/SeeAllType.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.constants
2 |
3 | /**
4 | * Enum class representing different types for "See All" scenarios, typically used in movie listings.
5 | */
6 | enum class SeeAllType {
7 | TRENDING,
8 | TOP_RATED,
9 | UPCOMING
10 | }
--------------------------------------------------------------------------------
/core/navigation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Movies
4 | Search
5 | Watch List
6 | Profile
7 |
--------------------------------------------------------------------------------
/core/network/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lütfen daha sonra tekrar deneyin
6 | Lütfen internet bağlantınızı kontrol ediniz
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/navigation/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Filmler
4 | Ara
5 | İzleme Listesi
6 | Profil
7 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie_detail/MovieTrailer.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie_detail
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | data class MovieTrailer(
6 | val trailers: List
7 | )
8 |
9 | @Immutable
10 | data class Trailer(
11 | val name: String,
12 | val key: String
13 | )
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/DialogUiEvent.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | /**
4 | * Sealed interface representing different UI events related to dialogs.
5 | */
6 | sealed interface DialogUiEvent {
7 | data object Loading: DialogUiEvent
8 | data object Active: DialogUiEvent
9 | data object InActive: DialogUiEvent
10 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/SearchMovieUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class SearchMovieUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | operator fun invoke(query: String) = repository.searchMovie(query = query)
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetAllTopRatedMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetAllTopRatedMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | operator fun invoke() = repository.getAllTopRatedMovies()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetAllTrendingMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetAllTrendingMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | operator fun invoke() = repository.getAllTrendingMovies()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetAllUpcomingMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetAllUpcomingMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | operator fun invoke() = repository.getAllUpcomingMovies()
9 | }
--------------------------------------------------------------------------------
/feature/signup/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Şifre
4 | Şifreyi doğrula
5 | Film Dünyamıza Katıl, Keyfini Çıkar!
6 | Hesap Oluştur
7 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/connectivity/ConnectivityObserver.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.connectivity
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface ConnectivityObserver {
6 |
7 | fun observer(): Flow
8 |
9 | fun isNetworkAvailable(): Boolean
10 |
11 | enum class Status {
12 | Available, Unavailable, Lost
13 | }
14 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetActorMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetActorMoviesUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke(actorId: Int) = repository.getActorMovies(actorId)
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/ObserveWatchListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class ObserveWatchListUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | suspend operator fun invoke() = movieRepository.observeWatchList()
9 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/watch_list/WatchList.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.watch_list
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class WatchList(
7 | val movieId: Int,
8 | val movieName: String,
9 | val releaseYear: String,
10 | val voteAverage: Float,
11 | val voteCount: Int,
12 | val imageUrlPath: String?
13 | )
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/ScreenOrientation.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.platform.LocalConfiguration
6 |
7 | @Composable
8 | fun isScreenPortrait(): Boolean =
9 | LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetActorDetailsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetActorDetailsUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke(actorId: Int) = repository.getActorDetails(actorId)
9 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie/MovieRecommendations.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class RecommendedMovieContent(
7 | val id: Int,
8 | val movieName: String,
9 | val image: String,
10 | val releaseDate: String,
11 | val voteAverage: Double,
12 | val voteCount: Int
13 | )
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/GetUserEmailUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import javax.inject.Inject
5 |
6 | class GetUserEmailUseCase @Inject constructor(private val firebaseAuthClient: FirebaseAuthClient) {
7 |
8 | operator fun invoke() = firebaseAuthClient.getUserEmail()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetMovieCreditsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetMovieCreditsUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke(movieId: Int) = repository.getMovieCredits(movieId = movieId)
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetMovieDetailsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetMovieDetailsUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke(movieId: Int) = repository.getMovieDetails(movieId = movieId)
9 | }
--------------------------------------------------------------------------------
/feature/watch_list/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The movie removed from the watch list.
4 | My Watch List
5 | It looks like there are no movies here. You can add new films to spice up your watchlist!
6 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/firestore/GetMovieDataUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.firestore
2 |
3 | import com.ahmetocak.data.repository.firebase.FirebaseRepository
4 | import javax.inject.Inject
5 |
6 | class GetMovieDataUseCase @Inject constructor(private val firebaseRepository: FirebaseRepository) {
7 |
8 | operator fun invoke() = firebaseRepository.getMovieData()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetMovieTrailersUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetMovieTrailersUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke(movieId: Int) = repository.getMovieTrailers(movieId = movieId)
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetTrendingMoviesFirstPageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetTrendingMoviesFirstPageUseCase @Inject constructor(private val repository: MovieRepository) {
7 |
8 | suspend operator fun invoke() = repository.getTrendingMoviesFirstPage()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetUserMovieReviewsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetUserMovieReviewsUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | operator fun invoke(movieId: Int) = movieRepository.getUserMovieReviews(movieId)
9 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie_detail/NetworkMovieTrailer.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie_detail
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkMovieTrailer(
6 | @SerializedName("results")
7 | val trailers: List
8 | )
9 |
10 | data class NetworkTrailer(
11 | val name: String,
12 | val key: String
13 | )
--------------------------------------------------------------------------------
/feature/actor_details/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Doğum yeri
4 | Doğum günü
5 | Doğum ve ölüm günü
6 | Biyografi
7 | Oyuncunun Filmleri
8 |
--------------------------------------------------------------------------------
/feature/actor_details/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Place of birth
4 | Birthday
5 | Birthday and death day
6 | Biography
7 | Actor\'s Movies
8 |
--------------------------------------------------------------------------------
/feature/watch_list/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Film izleme listesinden çıkartıldı.
4 | İzleme Listem
5 | Burada hiç film yok gibi görünüyor. Yeni film ekleyerek izleme listeni renklendirebilirsin!
6 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetMovieRecommendationsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetMovieRecommendationsUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | operator fun invoke(movieId: Int) = movieRepository.getMovieRecommendations(movieId)
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetTopRatedMoviesFirstPageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetTopRatedMoviesFirstPageUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | suspend operator fun invoke() = movieRepository.getTopRatedMoviesFirstPage()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/GetUpcomingMoviesFirstPageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class GetUpcomingMoviesFirstPageUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | suspend operator fun invoke() = movieRepository.getUpcomingMoviesFirstPage()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/preferences/ObserveAppThemeUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.preferences
2 |
3 | import com.ahmetocak.data.repository.datastore.DataStoreRepository
4 | import javax.inject.Inject
5 |
6 | class ObserveAppThemeUseCase @Inject constructor(private val preferencesRepository: DataStoreRepository) {
7 |
8 | suspend operator fun invoke() = preferencesRepository.observeAppTheme()
9 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/storage/GetUserProfileImageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.storage
2 |
3 | import com.ahmetocak.data.repository.firebase.FirebaseRepository
4 | import javax.inject.Inject
5 |
6 | class GetUserProfileImageUseCase @Inject constructor(private val firebaseRepository: FirebaseRepository) {
7 |
8 | operator fun invoke() = firebaseRepository.getUserProfileImage()
9 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie_detail/MovieCredit.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie_detail
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | data class MovieCredit(
6 | val cast: List,
7 | val directorName: String
8 | )
9 |
10 | @Immutable
11 | data class Cast(
12 | val id: Int,
13 | val name: String,
14 | val characterName: String,
15 | val imageUrlPath: String
16 | )
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/ConditionalModifier.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | inline fun Modifier.conditional(
6 | condition: Boolean,
7 | ifTrue: Modifier.() -> Modifier,
8 | ifFalse: Modifier.() -> Modifier = { this },
9 | ): Modifier = if (condition) {
10 | then(ifTrue(Modifier))
11 | } else {
12 | then(ifFalse(Modifier))
13 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/preferences/ObserveDynamicColorUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.preferences
2 |
3 | import com.ahmetocak.data.repository.datastore.DataStoreRepository
4 | import javax.inject.Inject
5 |
6 | class ObserveDynamicColorUseCase @Inject constructor(private val preferencesRepository: DataStoreRepository) {
7 |
8 | suspend operator fun invoke() = preferencesRepository.observeDynamicColor()
9 | }
--------------------------------------------------------------------------------
/feature/search/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lütfen bu alanı doldurunuz
4 | Aradığınız film bulunamadı.
5 | "Hey! İlgini çeken bir film mi var? Hemen ara ve keşfet! 🎬"
6 | Ara
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/Response.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | /**
4 | * Sealed interface representing a response, which can be either a success with data or an error.
5 | *
6 | * @param T The type of data associated with the response.
7 | */
8 | sealed interface Response {
9 | class Success(val data: T): Response
10 | class Error(val errorMessage: UiText): Response
11 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie/ActorDetails.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class ActorDetails(
7 | val id: Int,
8 | val biography: String,
9 | val birthday: String,
10 | val homepage: String?,
11 | val name: String,
12 | val deathDay: String?,
13 | val placeOfBirth: String,
14 | val profileImagePath: String
15 | )
16 |
--------------------------------------------------------------------------------
/core/ui/src/test/java/com/ahmetocak/ui/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.ui
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/search/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Please fill in this field
4 | Search
5 | The movie you are looking for could not be found.
6 | Hey! Is there a movie that interests you? Search and discover now! 🎬
7 |
--------------------------------------------------------------------------------
/feature/signup/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Email
4 | Password
5 | Confirm Password
6 | Join Our Movie World, Enjoy the Experience!
7 | Sign Up
8 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/RemoveMovieFromWatchListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import javax.inject.Inject
5 |
6 | class RemoveMovieFromWatchListUseCase @Inject constructor(private val movieRepository: MovieRepository) {
7 |
8 | suspend operator fun invoke(movieId: Int) =
9 | movieRepository.removeMovieFromWatchList(movieId = movieId)
10 | }
--------------------------------------------------------------------------------
/core/data/src/main/java/com/ahmetocak/data/repository/datastore/DataStoreRepository.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.data.repository.datastore
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface DataStoreRepository {
6 |
7 | suspend fun observeAppTheme(): Flow
8 |
9 | suspend fun updateAppTheme(isDarkMode: Boolean)
10 |
11 | suspend fun observeDynamicColor(): Flow
12 |
13 | suspend fun updateDynamicColor(dynamicColor: Boolean)
14 | }
--------------------------------------------------------------------------------
/core/common/src/test/java/com/ahmetocak/common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/db/WatchListDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.db
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.ahmetocak.database.dao.WatchListDao
6 | import com.ahmetocak.database.entity.WatchListEntity
7 |
8 | @Database(entities = [WatchListEntity::class], version = 1)
9 | abstract class WatchListDatabase : RoomDatabase() {
10 |
11 | abstract fun watchListDao(): WatchListDao
12 | }
--------------------------------------------------------------------------------
/feature/login/src/test/java/com/ahmetocak/login/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.login
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/movies/src/test/java/com/ahmetocak/movies/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movies
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/search/src/test/java/com/ahmetocak/search/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.search
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/signup/src/test/java/com/ahmetocak/signup/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.signup
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/core/datastore/src/main/java/com/ahmetocak/datastore/datasource/MovieAppPreferenceDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.datastore.datasource
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface MovieAppPreferenceDataSource {
6 |
7 | suspend fun observeAppTheme(): Flow
8 |
9 | suspend fun updateAppTheme(darkMode: Boolean)
10 |
11 | suspend fun observeDynamicColor(): Flow
12 |
13 | suspend fun updateDynamicColor(dynamicColor: Boolean)
14 | }
--------------------------------------------------------------------------------
/feature/profile/src/test/java/com/ahmetocak/profile/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.profile
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/see_all/src/test/java/com/ahmetocak/see_all/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.see_all
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/preferences/UpdateAppThemeUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.preferences
2 |
3 | import com.ahmetocak.data.repository.datastore.DataStoreRepository
4 | import javax.inject.Inject
5 |
6 | class UpdateAppThemeUseCase @Inject constructor(private val preferencesRepository: DataStoreRepository) {
7 |
8 | suspend operator fun invoke(isDarkMode: Boolean) =
9 | preferencesRepository.updateAppTheme(isDarkMode = isDarkMode)
10 | }
--------------------------------------------------------------------------------
/core/navigation/src/test/java/com/ahmetocak/navigation/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.navigation
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/core/designsystem/src/test/java/com/ahmetocak/designsystem/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/watch_list/src/test/java/com/ahmetocak/watch_list/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.watch_list
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/utils/ResponseMapper.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.utils
2 |
3 | import com.ahmetocak.common.helpers.Response
4 |
5 | fun Response.mapResponse(mapper: (I) -> O): Response {
6 | return when (this) {
7 | is Response.Success -> {
8 | Response.Success(mapper.invoke(this.data))
9 | }
10 | is Response.Error -> {
11 | Response.Error(errorMessage)
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/storage/UploadProfileImageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.storage
2 |
3 | import android.net.Uri
4 | import com.ahmetocak.data.repository.firebase.FirebaseRepository
5 | import javax.inject.Inject
6 |
7 | class UploadProfileImageUseCase @Inject constructor(private val firebaseRepository: FirebaseRepository) {
8 |
9 | operator fun invoke(imageUri: Uri) = firebaseRepository.uploadProfileImage(imageUri = imageUri)
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie/ActorMovies.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class ActorMovies(
7 | val movies: List
8 | )
9 |
10 | @Immutable
11 | data class ActorMoviesContent(
12 | val id: Int,
13 | val movieName: String,
14 | val posterImagePath: String?,
15 | val releaseDate: String,
16 | val voteAverage: Double,
17 | val voteCount: Int
18 | )
19 |
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie/Movie.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class Movie(
7 | val movies: List,
8 | val totalPages: Int
9 | )
10 |
11 | @Immutable
12 | data class MovieContent(
13 | val id: Int,
14 | val posterImagePath: String?,
15 | val releaseDate: String,
16 | val movieName: String,
17 | val voteAverage: Double,
18 | val voteCount: Int?
19 | )
--------------------------------------------------------------------------------
/feature/actor_details/src/test/java/com/ahmetocak/actor_details/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.actor_details
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/movie_details/src/test/java/com/ahmetocak/movie_details/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movie_details
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/preferences/UpdateDynamicColorUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.preferences
2 |
3 | import com.ahmetocak.data.repository.datastore.DataStoreRepository
4 | import javax.inject.Inject
5 |
6 | class UpdateDynamicColorUseCase @Inject constructor(private val preferencesRepository: DataStoreRepository) {
7 |
8 | suspend operator fun invoke(isDynamicColor: Boolean) =
9 | preferencesRepository.updateDynamicColor(dynamicColor = isDynamicColor)
10 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/firebase/storage/FirebaseStorageDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.firebase.storage
2 |
3 | import android.net.Uri
4 | import com.google.android.gms.tasks.Task
5 | import com.google.firebase.storage.UploadTask
6 |
7 | interface FirebaseStorageDataSource {
8 |
9 | fun uploadProfileImage(imageUri: Uri): UploadTask
10 |
11 | fun getUserProfileImage(): Task
12 |
13 | fun deleteUserProfileImage(): Task
14 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/movie/AddMovieToDbWatchListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.movie
2 |
3 | import com.ahmetocak.data.repository.movie.MovieRepository
4 | import com.ahmetocak.model.firebase.WatchListMovie
5 | import javax.inject.Inject
6 |
7 | class AddMovieToDbWatchListUseCase @Inject constructor(private val repository: MovieRepository) {
8 |
9 | suspend operator fun invoke(watchListMovie: WatchListMovie) =
10 | repository.addMovieToWatchList(watchListMovie = watchListMovie)
11 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/firebase/WatchListMovie.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.firebase
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | data class WatchList(
6 | val watchList: List = emptyList()
7 | )
8 |
9 | @Immutable
10 | data class WatchListMovie(
11 | val id: Int? = null,
12 | val name: String? = null,
13 | val releaseYear: String? = null,
14 | val voteAverage: Float? = null,
15 | val voteCount: Int? = null,
16 | val imageUrlPath: String? = null
17 | )
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/firebase/firestore/NetworkWatchListMovie.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.firebase.firestore
2 |
3 | data class NetworkWatchList(
4 | val watchList: List = emptyList()
5 | )
6 |
7 | data class NetworkWatchListMovie(
8 | val id: Int? = null,
9 | val name: String? = null,
10 | val releaseYear: String? = null,
11 | val voteAverage: Float? = null,
12 | val voteCount: Int? = null,
13 | val imageUrlPath: String? = null
14 | )
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/UiState.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import androidx.compose.runtime.Stable
4 |
5 | /**
6 | * Sealed interface representing different UI states with associated data.
7 | *
8 | * @param T The type of data associated with the UI state.
9 | */
10 | @Stable
11 | sealed interface UiState {
12 | data object Loading: UiState
13 | data class OnDataLoaded(val data: T): UiState
14 | data class OnError(val errorMessage: UiText): UiState
15 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie/UserMovieReviews.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class UserReviewResults(
7 | val id: String,
8 | val author: String,
9 | val content: String,
10 | val createdAt: String,
11 | val updatedAt: String,
12 | val authorDetails: UserReviewAuthorDetails
13 | )
14 |
15 | @Immutable
16 | data class UserReviewAuthorDetails(
17 | val rating: Int? = null,
18 | val avatarPath: String? = null
19 | )
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/firestore/UpdateMovieDataUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.firestore
2 |
3 | import com.ahmetocak.data.repository.firebase.FirebaseRepository
4 | import com.ahmetocak.model.firebase.WatchListMovie
5 | import javax.inject.Inject
6 |
7 | class UpdateMovieDataUseCase @Inject constructor(private val firebaseRepository: FirebaseRepository) {
8 |
9 | operator fun invoke(watchListMovie: List) =
10 | firebaseRepository.updateMovieData(watchListMovie = watchListMovie)
11 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/firestore/AddMovieToWatchListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.firestore
2 |
3 | import com.ahmetocak.data.repository.firebase.FirebaseRepository
4 | import com.ahmetocak.model.firebase.WatchListMovie
5 | import javax.inject.Inject
6 |
7 | class AddMovieToWatchListUseCase @Inject constructor(private val firebaseRepository: FirebaseRepository) {
8 |
9 | operator fun invoke(watchListMovie: WatchListMovie) =
10 | firebaseRepository.addMovieToFirestore(watchListMovie = watchListMovie)
11 | }
--------------------------------------------------------------------------------
/core/model/src/main/java/com/ahmetocak/model/movie_detail/MovieDetail.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.model.movie_detail
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class MovieDetail(
7 | val id: Int,
8 | val genres: String,
9 | val overview: String,
10 | val posterImageUrlPath: String,
11 | val backdropImageUrlPath: String,
12 | val movieName: String,
13 | val voteAverage: Float,
14 | val voteCount: Int,
15 | val releaseDate: String,
16 | val duration: Int,
17 | val originalMovieName: String
18 | )
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/ShowUserMessage.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import android.widget.Toast
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.platform.LocalContext
6 |
7 | @Composable
8 | inline fun ShowUserMessage(
9 | message: String,
10 | toastLength: Int = Toast.LENGTH_LONG,
11 | consumedMessage: () -> Unit = {}
12 | ) {
13 | Toast.makeText(
14 | LocalContext.current,
15 | message,
16 | toastLength
17 | ).show()
18 | consumedMessage()
19 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie/NetworkActorDetails.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkActorDetails(
6 | val id: Int,
7 | val biography: String?,
8 | val birthday: String?,
9 | val homepage: String?,
10 | val name: String?,
11 |
12 | @SerializedName("deathday")
13 | val deathDay: String?,
14 |
15 | @SerializedName("place_of_birth")
16 | val placeOfBirth: String?,
17 |
18 | @SerializedName("profile_path")
19 | val profileImagePath: String?
20 | )
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/di/DaoModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.di
2 |
3 | import com.ahmetocak.database.dao.WatchListDao
4 | import com.ahmetocak.database.db.WatchListDatabase
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | object DaoModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideWatchListDao(db: WatchListDatabase): WatchListDao {
18 | return db.watchListDao()
19 | }
20 | }
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/datasource/WatchListLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.datasource
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.database.entity.WatchListEntity
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface WatchListLocalDataSource {
8 |
9 | suspend fun addMovieToWatchList(watchListEntity: WatchListEntity): Response
10 |
11 | suspend fun observeWatchList(): Response>>
12 |
13 | suspend fun removeMovieFromWatchList(movieId: Int): Response
14 |
15 | suspend fun deleteWatchList(): Response
16 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/firebase/firestore/FirebaseFirestoreDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.firebase.firestore
2 |
3 | import com.ahmetocak.network.model.firebase.firestore.NetworkWatchListMovie
4 | import com.google.android.gms.tasks.Task
5 | import com.google.firebase.firestore.DocumentSnapshot
6 |
7 | interface FirebaseFirestoreDataSource {
8 |
9 | fun addMovieData(watchListMovie: NetworkWatchListMovie): Task
10 |
11 | fun updateMovieData(watchListMovie: List): Task
12 |
13 | fun getMovieData(): Task
14 |
15 | fun deleteMovieDocument(): Task
16 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie_detail/NetworkMovieCredit.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie_detail
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkMovieCredit(
6 | val cast: List,
7 |
8 | @SerializedName("crew")
9 | val crew: List
10 | )
11 |
12 | data class NetworkCast(
13 | val id: Int,
14 | val name: String?,
15 |
16 | @SerializedName("character")
17 | val characterName: String?,
18 |
19 | @SerializedName("profile_path")
20 | val imageUrlPath: String?
21 | )
22 |
23 | data class NetworkCrew(
24 | val name: String?,
25 | val job: String?
26 | )
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/entity/WatchListEntity.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity
8 | data class WatchListEntity(
9 |
10 | @PrimaryKey
11 | @ColumnInfo("id")
12 | val movieId: Int,
13 |
14 | @ColumnInfo("movie_name")
15 | val movieName: String,
16 |
17 | @ColumnInfo("release_year")
18 | val releaseYear: String,
19 |
20 | @ColumnInfo("vote_average")
21 | val voteAverage: Float,
22 |
23 | @ColumnInfo("vote_count")
24 | val voteCount: Int,
25 |
26 | @ColumnInfo("image_url_path")
27 | val imageUrlPath: String?
28 | )
29 |
--------------------------------------------------------------------------------
/core/data/src/main/java/com/ahmetocak/data/mapper/FirebaseMapper.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.data.mapper
2 |
3 | import com.ahmetocak.model.firebase.WatchListMovie
4 | import com.ahmetocak.network.model.firebase.firestore.NetworkWatchListMovie
5 |
6 | internal fun WatchListMovie.toNetworkWatchListMovie(): NetworkWatchListMovie {
7 | return NetworkWatchListMovie(
8 | id = id,
9 | name = name,
10 | releaseYear = releaseYear,
11 | voteAverage = voteAverage,
12 | voteCount = voteCount,
13 | imageUrlPath = imageUrlPath
14 | )
15 | }
16 |
17 | internal fun List.toNetworkWatchListMovie(): List {
18 | return this.map { it.toNetworkWatchListMovie() }
19 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/AdaptiveSizeSetter.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.unit.Dp
5 |
6 | @Composable
7 | fun setSize(
8 | windowSizeClasses: WindowSizeClasses = computeWindowHeightSize() ?: WindowSizeClasses.MEDIUM,
9 | onCompact: Dp,
10 | onMedium: Dp,
11 | onExpanded: Dp
12 | ): Dp {
13 | return when (windowSizeClasses) {
14 | is WindowSizeClasses.COMPACT -> {
15 | onCompact
16 | }
17 |
18 | is WindowSizeClasses.MEDIUM -> {
19 | onMedium
20 | }
21 |
22 | is WindowSizeClasses.EXPANDED -> {
23 | onExpanded
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/di/DatasourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.di
2 |
3 | import com.ahmetocak.database.dao.WatchListDao
4 | import com.ahmetocak.database.datasource.WatchListLocalDataSource
5 | import com.ahmetocak.database.datasource.WatchListLocalDataSourceImpl
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | @InstallIn(SingletonComponent::class)
14 | object DatasourceModule {
15 |
16 | @Singleton
17 | @Provides
18 | fun provideWatchListLocalDataSource(dao: WatchListDao): WatchListLocalDataSource {
19 | return WatchListLocalDataSourceImpl(dao)
20 | }
21 | }
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/HandleTaskError.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import com.ahmetocak.common.R
4 |
5 | /**
6 | * Handles an exception from a task operation and returns a [UiText] representation of the error message.
7 | *
8 | * @param e The exception to be handled.
9 | * @param defaultErrorMessage The default [UiText] to be used when the exception does not provide a message.
10 | * @return A UiText representation of the error message.
11 | */
12 | fun handleTaskError(
13 | e: Exception?,
14 | defaultErrorMessage: UiText = UiText.StringResource(R.string.unknown_error)
15 | ): UiText {
16 | return e?.message?.let { errorMessage ->
17 | UiText.DynamicString(errorMessage)
18 | } ?: defaultErrorMessage
19 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/dimens/Dimens.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.dimens
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object Dimens {
6 | val oneLevelPadding = 8.dp
7 | val twoLevelPadding = 16.dp
8 | val threeLevelPadding = 24.dp
9 | val fourLevelPadding = 32.dp
10 | val fiveLevelPadding = 40.dp
11 | val sixLevelPadding = 48.dp
12 | val sevenLevelPadding = 56.dp
13 | val eightLevelPadding = 64.dp
14 | }
15 |
16 | object ComponentDimens {
17 | val buttonHeight = 56.dp
18 | val emptyWarningImageSize = 112.dp
19 | val buttonCircularProgressIndicatorSize = 24.dp
20 | val errorViewIconSize = 64.dp
21 | val appIconSize = 96.dp
22 | val profileImageSize = 144.dp
23 | val searchSomethingViewSize = 112.dp
24 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie/NetworkActorMovies.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkActorMovies(
6 | @SerializedName("cast")
7 | val movies: List
8 | )
9 |
10 | data class NetworkActorMoviesContent(
11 | val id: Int,
12 |
13 | @SerializedName("title")
14 | val movieName: String? = null,
15 |
16 | @SerializedName("poster_path")
17 | val posterImagePath: String? = null,
18 |
19 | @SerializedName("release_date")
20 | val releaseDate: String? = null,
21 |
22 | @SerializedName("vote_average")
23 | val voteAverage: Double? = null,
24 |
25 | @SerializedName("vote_count")
26 | val voteCount: Int? = null
27 | )
28 |
--------------------------------------------------------------------------------
/core/ui/src/androidTest/java/com/ahmetocak/ui/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.ui
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.ui.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/authentication/src/main/java/com/ahmetocak/authentication/di/AuthModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.authentication.di
2 |
3 | import android.content.Context
4 | import com.ahmetocak.authentication.firebase.GoogleAuthClient
5 | import com.google.android.gms.auth.api.identity.Identity
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object AuthModule {
16 |
17 | @Singleton
18 | @Provides
19 | fun provideGoogleAuthClient(@ApplicationContext context: Context): GoogleAuthClient {
20 | return GoogleAuthClient(context, Identity.getSignInClient(context))
21 | }
22 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie/NetworkMovie.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkMovie(
6 | @SerializedName("results")
7 | val movies: List,
8 |
9 | @SerializedName("total_pages")
10 | val totalPages: Int
11 | )
12 |
13 | data class NetworkMovieContent(
14 | val id: Int,
15 |
16 | @SerializedName("poster_path")
17 | val posterImagePath: String?,
18 |
19 | @SerializedName("release_date")
20 | val releaseDate: String?,
21 |
22 | @SerializedName("original_title")
23 | val movieName: String?,
24 |
25 | @SerializedName("vote_average")
26 | val voteAverage: Double?,
27 |
28 | @SerializedName("vote_count")
29 | val voteCount: Int?
30 | )
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ahmetocak/movieapp/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movieapp
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.movieapp", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/common/src/androidTest/java/com/ahmetocak/common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.common.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/login/src/androidTest/java/com/ahmetocak/login/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.login
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.login.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/movies/src/androidTest/java/com/ahmetocak/movies/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movies
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.movies.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/search/src/androidTest/java/com/ahmetocak/search/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.search
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.search.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/signup/src/androidTest/java/com/ahmetocak/signup/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.signup
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.signup.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/data/src/main/java/com/ahmetocak/data/repository/firebase/FirebaseRepository.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.data.repository.firebase
2 |
3 | import android.net.Uri
4 | import com.ahmetocak.model.firebase.WatchListMovie
5 | import com.google.android.gms.tasks.Task
6 | import com.google.firebase.firestore.DocumentSnapshot
7 | import com.google.firebase.storage.UploadTask
8 |
9 | interface FirebaseRepository {
10 |
11 | fun addMovieToFirestore(watchListMovie: WatchListMovie): Task
12 |
13 | fun updateMovieData(watchListMovie: List): Task
14 |
15 | fun getMovieData(): Task
16 |
17 | fun deleteMovieDocument(): Task
18 |
19 | fun uploadProfileImage(imageUri: Uri): UploadTask
20 |
21 | fun getUserProfileImage(): Task
22 |
23 | fun deleteUserProfileImage(): Task
24 | }
--------------------------------------------------------------------------------
/core/ui/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/profile/src/androidTest/java/com/ahmetocak/profile/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.profile
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.profile.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/see_all/src/androidTest/java/com/ahmetocak/see_all/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.see_all
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.see_all.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/common/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/auth/AuthWelcomeMessage.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components.auth
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.text.style.TextAlign
9 | import com.ahmetocak.designsystem.dimens.setAdaptiveWidth
10 |
11 | @Composable
12 | fun AuthWelcomeMessage(
13 | modifier: Modifier = Modifier,
14 | text: String
15 | ) {
16 | Text(
17 | modifier = modifier.setAdaptiveWidth(),
18 | text = text,
19 | textAlign = TextAlign.Center,
20 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.W500),
21 | )
22 | }
--------------------------------------------------------------------------------
/feature/login/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/dao/WatchListDao.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.ahmetocak.database.entity.WatchListEntity
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | @Dao
11 | interface WatchListDao {
12 |
13 | @Insert(onConflict = OnConflictStrategy.REPLACE)
14 | suspend fun addMovieToWatchList(watchListEntity: WatchListEntity)
15 |
16 | @Query("SELECT * FROM WatchListEntity")
17 | fun observeWatchList(): Flow>
18 |
19 | @Query("DELETE FROM WatchListEntity WHERE id == :movieId")
20 | suspend fun removeMovieFromWatchList(movieId: Int)
21 |
22 | @Query("DELETE FROM WatchListEntity")
23 | suspend fun deleteWatchList()
24 | }
--------------------------------------------------------------------------------
/core/designsystem/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/navigation/src/androidTest/java/com/ahmetocak/navigation/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.navigation
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.navigation.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/movies/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/profile/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/search/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/see_all/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/signup/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/watch_list/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/watch_list/src/androidTest/java/com/ahmetocak/watch_list/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.watch_list
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.watch_list.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/core/authentication/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/designsystem/src/androidTest/java/com/ahmetocak/designsystem/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.designsystem.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/actor_details/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature/movie_details/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import android.util.Patterns
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.res.stringResource
9 |
10 | @Composable
11 | fun Int.convertToDurationTime(): String =
12 | "${this / 60}${stringResource(id = R.string.hour_text)} ${this % 60}${stringResource(id = R.string.min_tex)}"
13 |
14 | @Composable
15 | fun Float.roundToDecimal(): String = String.format("%.1f", this)
16 |
17 | fun Context.findActivity(): Activity? = when (this) {
18 | is Activity -> this
19 | is ContextWrapper -> baseContext.findActivity()
20 | else -> null
21 | }
22 |
23 | fun String.isValidEmail(): Boolean = Patterns.EMAIL_ADDRESS.matcher(this).matches()
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/SendResetPasswordEmailUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.common.helpers.handleTaskError
6 | import javax.inject.Inject
7 |
8 | class SendResetPasswordEmailUseCase @Inject constructor(private val firebaseAuthClient: FirebaseAuthClient) {
9 |
10 | operator fun invoke(email: String, onTaskSuccess: () -> Unit, onTaskFailed: (UiText) -> Unit) {
11 | firebaseAuthClient.sendResetPasswordEmail(email).addOnCompleteListener { task ->
12 | if (task.isSuccessful) {
13 | onTaskSuccess()
14 | } else {
15 | onTaskFailed(handleTaskError(task.exception))
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/di/ApiModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.di
2 |
3 | import com.ahmetocak.network.helpers.NetworkConstants
4 | import com.ahmetocak.network.api.MovieApi
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import retrofit2.Retrofit
10 | import retrofit2.converter.gson.GsonConverterFactory
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object ApiModule {
16 |
17 | @Singleton
18 | @Provides
19 | fun provideMovieApi(): MovieApi {
20 | return Retrofit.Builder()
21 | .baseUrl(NetworkConstants.BASE_URL)
22 | .addConverterFactory(GsonConverterFactory.create())
23 | .build()
24 | .create(MovieApi::class.java)
25 | }
26 | }
--------------------------------------------------------------------------------
/feature/actor_details/src/androidTest/java/com/ahmetocak/actor_details/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.actor_details
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.actor_details.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/movie_details/src/androidTest/java/com/ahmetocak/movie_details/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movie_details
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.ahmetocak.movie_details.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/movie_details/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Movie added to watch list.
4 | The movie removed from the watch list.
5 | Director
6 | Actors
7 | Trailers
8 | reviews
9 | Updated at
10 | User Reviews
11 | Recommendations
12 | Can you tell me about %s movie ?
13 | We are currently unable to display movie details due to some issues. Please try again later.
14 |
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/di/DatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.di
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import com.ahmetocak.database.db.WatchListDatabase
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.android.qualifiers.ApplicationContext
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | object DatabaseModule {
17 |
18 | @Provides
19 | @Singleton
20 | fun provideWatchListDatabase(@ApplicationContext context: Context): WatchListDatabase {
21 | return Room.databaseBuilder(
22 | context,
23 | WatchListDatabase::class.java,
24 | "watch_list_db"
25 | ).build()
26 | }
27 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/LoginUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.common.helpers.handleTaskError
6 | import javax.inject.Inject
7 |
8 | class LoginUseCase @Inject constructor(private val firebaseAuthClient: FirebaseAuthClient) {
9 |
10 | operator fun invoke(
11 | email: String,
12 | password: String,
13 | onTaskSuccess: () -> Unit,
14 | onTaskFailed: (UiText) -> Unit
15 | ) {
16 | firebaseAuthClient.login(email, password).addOnCompleteListener { task ->
17 | if (task.isSuccessful) {
18 | onTaskSuccess()
19 | } else {
20 | onTaskFailed(handleTaskError(task.exception))
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/core/ui/src/main/java/com/ahmetocak/ui/TmdbLogo.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.ui
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.res.painterResource
10 | import androidx.compose.ui.unit.Dp
11 | import androidx.compose.ui.unit.dp
12 | import com.ahmetocak.designsystem.theme.TmdbBlue
13 |
14 | @Composable
15 | fun TmdbLogo(modifier: Modifier = Modifier, logoSize: Dp = 96.dp) {
16 | Image(
17 | modifier = modifier
18 | .size(logoSize)
19 | .background(TmdbBlue)
20 | .padding(4.dp),
21 | painter = painterResource(id = R.drawable.tmdb_logo),
22 | contentDescription = null
23 | )
24 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/SignUpUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.common.helpers.handleTaskError
6 | import javax.inject.Inject
7 |
8 | class SignUpUseCase @Inject constructor(private val firebaseAuthClient: FirebaseAuthClient) {
9 |
10 | operator fun invoke(
11 | email: String,
12 | password: String,
13 | onTaskSuccess: () -> Unit,
14 | onTaskFailed: (UiText) -> Unit
15 | ) {
16 | firebaseAuthClient.signUp(email, password).addOnCompleteListener { task ->
17 | if (task.isSuccessful) {
18 | onTaskSuccess()
19 | } else {
20 | onTaskFailed(handleTaskError(task.exception))
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie/NetworkMovieRecommendations.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkMovieRecommendations(
6 | val page: Int,
7 |
8 | @SerializedName("results")
9 | val recommendations: List,
10 |
11 | @SerializedName("total_pages")
12 | val totalPages: Int
13 | )
14 |
15 | data class NetworkRecommendedMovieContent(
16 | val id: Int,
17 |
18 | @SerializedName("original_title")
19 | val movieName: String?,
20 |
21 | @SerializedName("poster_path")
22 | val image: String?,
23 |
24 | @SerializedName("release_date")
25 | val releaseDate: String?,
26 |
27 | @SerializedName("vote_average")
28 | val voteAverage: Double?,
29 |
30 | @SerializedName("vote_count")
31 | val voteCount: Int?
32 | )
--------------------------------------------------------------------------------
/feature/profile/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ayarlar
4 | Karanlık Tema
5 | Diller
6 | İngilizce
7 | Türkçe
8 | Dinamik Renk
9 | Gönder
10 | İptal
11 | Hesabı Sil
12 | Hesabınızı silmek istediğinizden emin misiniz ? Hesabınızı sildiğinizde tüm kullanıcı bilgileriniz de silinir. Bu süreç geri döndürülemez.
13 | Sil
14 | Şifre
15 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/SignOutUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import com.ahmetocak.common.helpers.Response
5 | import com.ahmetocak.common.helpers.UiText
6 | import com.ahmetocak.data.repository.movie.MovieRepository
7 | import javax.inject.Inject
8 |
9 | class SignOutUseCase @Inject constructor(
10 | private val firebaseAuthClient: FirebaseAuthClient,
11 | private val movieRepository: MovieRepository
12 | ) {
13 | suspend operator fun invoke(onFailed: (UiText) -> Unit) {
14 | when(val response = movieRepository.deleteWatchList()) {
15 | is Response.Success -> {
16 | firebaseAuthClient.signOut()
17 | }
18 | is Response.Error -> {
19 | onFailed(response.errorMessage)
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/feature/profile/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Settings
4 | Dark Theme
5 | Languages
6 | English
7 | Turkish
8 | Dynamic Color
9 | Send
10 | Cancel
11 | Delete Account
12 | Are you sure you want to delete your account ? When you delete your account, all your user information is also deleted. This process is irreversible.
13 | Delete
14 | Password
15 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/di/FirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.di
2 |
3 | import com.google.firebase.firestore.FirebaseFirestore
4 | import com.google.firebase.firestore.ktx.firestore
5 | import com.google.firebase.ktx.Firebase
6 | import com.google.firebase.storage.FirebaseStorage
7 | import com.google.firebase.storage.ktx.storage
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | @InstallIn(SingletonComponent::class)
16 | object FirebaseModule {
17 |
18 | @Singleton
19 | @Provides
20 | fun provideFirebaseFirestore(): FirebaseFirestore {
21 | return Firebase.firestore
22 | }
23 |
24 | @Singleton
25 | @Provides
26 | fun provideFirebaseStorageReference(): FirebaseStorage {
27 | return Firebase.storage
28 | }
29 | }
--------------------------------------------------------------------------------
/core/data/src/main/java/com/ahmetocak/data/repository/datastore/DataStoreRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.data.repository.datastore
2 |
3 | import com.ahmetocak.datastore.datasource.MovieAppPreferenceDataSource
4 | import kotlinx.coroutines.flow.Flow
5 | import javax.inject.Inject
6 |
7 | class DataStoreRepositoryImpl @Inject constructor(
8 | private val preferenceDataSource: MovieAppPreferenceDataSource
9 | ) : DataStoreRepository {
10 |
11 | override suspend fun observeAppTheme(): Flow = preferenceDataSource.observeAppTheme()
12 |
13 | override suspend fun updateAppTheme(isDarkMode: Boolean) =
14 | preferenceDataSource.updateAppTheme(isDarkMode)
15 |
16 | override suspend fun observeDynamicColor(): Flow = preferenceDataSource.observeDynamicColor()
17 |
18 | override suspend fun updateDynamicColor(dynamicColor: Boolean) =
19 | preferenceDataSource.updateDynamicColor(dynamicColor)
20 | }
--------------------------------------------------------------------------------
/feature/movie_details/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Film izleme listesine eklendi.
4 | Film izleme listesinden çıkartıldı.
5 | Yönetmen
6 | Oyuncu Kadrosu
7 | Fragmanlar
8 | incelemeler
9 | Güncellendi
10 | Kullanıcı Değerlendirmeleri
11 | Önerilenler
12 | Bana %s filminden bahseder misin ?
13 | Şu an için bazı sorunlardan ötürü film detaylarını gösteremiyoruz. Lütfen daha sonra tekrar deneyin.
14 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie/NetworkUserMovieReview.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkUserMovieReviews(
6 | val page: Int,
7 | val results: List,
8 |
9 | @SerializedName("total_pages")
10 | val totalPages: Int,
11 | )
12 |
13 | data class NetworkUserReviewResults(
14 | val id: String,
15 | val author: String? = null,
16 | val content: String? = null,
17 |
18 | @SerializedName("created_at")
19 | val createdAt: String,
20 |
21 | @SerializedName("updated_at")
22 | val updatedAt: String? = null,
23 |
24 | @SerializedName("author_details")
25 | val authorDetails: NetworkUserReviewAuthorDetails
26 | )
27 |
28 | data class NetworkUserReviewAuthorDetails(
29 | val rating: Int? = null,
30 |
31 | @SerializedName("avatar_path")
32 | val avatarPath: String? = null
33 | )
--------------------------------------------------------------------------------
/feature/movie_details/src/main/res/drawable/ic_google_gemini.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/GetGeminiResponseUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.common.R as common
6 | import com.google.ai.client.generativeai.GenerativeModel
7 | import javax.inject.Inject
8 |
9 | class GetGeminiResponseUseCase @Inject constructor() {
10 |
11 | suspend operator fun invoke(prompt: String): Response {
12 | val generativeModel = GenerativeModel(
13 | modelName = "gemini-pro",
14 | apiKey = BuildConfig.GEMINI_API_KEY
15 | )
16 |
17 | return try {
18 | Response.Success(data = generativeModel.generateContent(prompt).text)
19 | } catch (e: Exception) {
20 | Response.Error(errorMessage = e.message?.let { message ->
21 | UiText.DynamicString(message)
22 | } ?: UiText.StringResource(common.string.unknown_error))
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/model/movie_detail/NetworkMovieDetail.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.model.movie_detail
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class NetworkMovieDetail(
6 | val id: Int,
7 | val genres: List,
8 | val overview: String?,
9 |
10 | @SerializedName("poster_path")
11 | val posterImageUrlPath: String?,
12 |
13 | @SerializedName("backdrop_path")
14 | val backdropImageUrlPath: String?,
15 |
16 | @SerializedName("title")
17 | val movieName: String?,
18 |
19 | @SerializedName("vote_average")
20 | val voteAverage: Double?,
21 |
22 | @SerializedName("vote_count")
23 | val voteCount: Int?,
24 |
25 | @SerializedName("release_date")
26 | val releaseDate: String?,
27 |
28 | @SerializedName("runtime")
29 | val duration: Int?,
30 |
31 | @SerializedName("original_title")
32 | val originalMovieName: String?
33 | )
34 |
35 | data class NetworkMovieGenre(
36 | val name: String
37 | )
--------------------------------------------------------------------------------
/core/common/src/main/res/values-tr-rTR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Bir şeyler ters gitti. Lütfen daha sonra tekrar deneyiniz!
4 | s
5 | d
6 | Filmler
7 | Ara
8 | İzleme Listesi
9 | Profil
10 | Lütfen bu alanı doldurunuz
11 | Şifreler uyuşmuyor
12 | Şifre minimum 6 karakterden oluşmalı
13 | Lütfen geçerli bir email adresi giriniz
14 | Aradığınız film bulunamadı.
15 | Trendler
16 | Top Rated
17 | Upcoming
18 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/Dialog.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.layout.ColumnScope
4 | import androidx.compose.foundation.layout.width
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material3.Card
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.platform.LocalConfiguration
10 | import androidx.compose.ui.unit.dp
11 | import androidx.compose.ui.window.Dialog
12 | import com.ahmetocak.designsystem.dimens.Dimens
13 |
14 | @Composable
15 | fun MovieDialog(
16 | onDismissRequest: () -> Unit,
17 | content: @Composable (ColumnScope.() -> Unit)
18 | ) {
19 | Dialog(onDismissRequest = onDismissRequest) {
20 | Card(
21 | modifier = Modifier.width(LocalConfiguration.current.screenWidthDp.dp - Dimens.fourLevelPadding),
22 | shape = RoundedCornerShape(28.dp),
23 | content = content
24 | )
25 | }
26 | }
--------------------------------------------------------------------------------
/core/common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Something went wrong. Please try again later!
4 | h
5 | m
6 | Movies
7 | Search
8 | Watch List
9 | Profile
10 | Please fill in this field
11 | Please enter a valid email address
12 | Passwords do not match
13 | Password must be at least 6 characters in length
14 | The movie you are looking for could not be found.
15 | Top Rated
16 | Upcoming
17 | Trending
18 |
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/datasource/WatchListLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.datasource
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.database.dao.WatchListDao
5 | import com.ahmetocak.database.entity.WatchListEntity
6 | import com.ahmetocak.database.utils.dbCall
7 | import kotlinx.coroutines.flow.Flow
8 | import javax.inject.Inject
9 |
10 | class WatchListLocalDataSourceImpl @Inject constructor(
11 | private val dao: WatchListDao
12 | ) : WatchListLocalDataSource {
13 | override suspend fun addMovieToWatchList(watchListEntity: WatchListEntity): Response =
14 | dbCall { dao.addMovieToWatchList(watchListEntity) }
15 |
16 | override suspend fun observeWatchList(): Response>> =
17 | dbCall { dao.observeWatchList() }
18 |
19 | override suspend fun removeMovieFromWatchList(movieId: Int) =
20 | dbCall { dao.removeMovieFromWatchList(movieId) }
21 |
22 | override suspend fun deleteWatchList(): Response = dbCall { dao.deleteWatchList() }
23 | }
--------------------------------------------------------------------------------
/core/database/src/main/java/com/ahmetocak/database/utils/DbCall.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.database.utils
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.database.R
6 |
7 | /**
8 | * A generic function for making database-related calls with error handling.
9 | *
10 | * This function is designed to be used with suspending database operations. It wraps the database
11 | * operation with a try-catch block to handle exceptions and provides a uniform way of dealing with responses.
12 | *
13 | * @param call A suspending lambda function representing the database operation.
14 | * @return A sealed class [Response] containing either the success data or an error message.
15 | */
16 | suspend inline fun dbCall(crossinline call: suspend () -> T): Response {
17 | return try {
18 | Response.Success(data = call())
19 | } catch (e: Exception) {
20 | Response.Error(errorMessage = e.message?.let { message ->
21 | UiText.DynamicString(message)
22 | } ?: UiText.StringResource(R.string.unknown_error))
23 | }
24 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven("https://jitpack.io")
14 | }
15 | }
16 |
17 | rootProject.name = "MovieApp"
18 | include(":app")
19 | include(":core")
20 | include(":core:ui")
21 | include(":core:designsystem")
22 | include(":core:data")
23 | include(":core:domain")
24 | include(":core:model")
25 | include(":core:network")
26 | include(":core:database")
27 | include(":core:datastore")
28 | include(":core:common")
29 | include(":feature")
30 | include(":feature:login")
31 | include(":feature:signup")
32 | include(":feature:movie_details")
33 | include(":feature:see_all")
34 | include(":feature:movies")
35 | include(":feature:profile")
36 | include(":feature:search")
37 | include(":feature:watch_list")
38 | include(":core:navigation")
39 | include(":feature:actor_details")
40 | include(":core:authentication")
41 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movieapp.presentation.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/core/model/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2 | plugins {
3 | alias(libs.plugins.androidLibrary)
4 | alias(libs.plugins.kotlin.android)
5 | kotlin("kapt")
6 | }
7 |
8 | android {
9 | namespace = "com.ahmetocak.model"
10 | compileSdk = ConfigData.compileSdk
11 |
12 | defaultConfig {
13 | minSdk = ConfigData.minSdk
14 |
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles("consumer-rules.pro")
17 | }
18 |
19 | buildTypes {
20 | release {
21 | isMinifyEnabled = BuildTypes.isMinifyEnabled
22 | proguardFiles(
23 | getDefaultProguardFile("proguard-android-optimize.txt"),
24 | "proguard-rules.pro"
25 | )
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_17
30 | targetCompatibility = JavaVersion.VERSION_17
31 | }
32 | kotlinOptions {
33 | jvmTarget = "17"
34 | }
35 | }
36 |
37 | dependencies {
38 |
39 | implementation(libs.androidx.core.ktx)
40 | implementation(libs.androidx.activity.compose)
41 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/helpers/ApiCall.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.helpers
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.common.helpers.UiText
5 | import com.ahmetocak.network.R
6 | import java.io.IOException
7 |
8 | /**
9 | * A generic function for making API calls with error handling.
10 | *
11 | * This function is designed to be used with suspending API calls. It wraps the API call
12 | * with try-catch blocks to handle exceptions and provides a uniform way of dealing with responses.
13 | *
14 | * @param call A suspending lambda function representing the API call.
15 | * @return A sealed class [Response] containing either the success data or an error message.
16 | */
17 | suspend inline fun apiCall(crossinline call: suspend () -> T): Response {
18 | return try {
19 | Response.Success(data = call())
20 | } catch (e: IOException) {
21 | Response.Error(errorMessage = UiText.StringResource(R.string.internet_error))
22 | } catch (e: Exception) {
23 | Response.Error(errorMessage = e.message?.let { message ->
24 | UiText.DynamicString(message)
25 | } ?: UiText.StringResource(R.string.unknown_error))
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ahmetocak/movieapp/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.movieapp.di
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import com.google.firebase.auth.FirebaseAuth
6 | import com.google.firebase.auth.ktx.auth
7 | import com.google.firebase.ktx.Firebase
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.android.qualifiers.ApplicationContext
12 | import dagger.hilt.components.SingletonComponent
13 | import kotlinx.coroutines.CoroutineDispatcher
14 | import kotlinx.coroutines.Dispatchers
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object AppModule {
20 |
21 | @Singleton
22 | @Provides
23 | fun provideNetworkConnectivityManager(@ApplicationContext context: Context): ConnectivityManager {
24 | return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
25 | }
26 |
27 | @Singleton
28 | @Provides
29 | fun provideFirebaseAuth(): FirebaseAuth {
30 | return Firebase.auth
31 | }
32 |
33 | @Singleton
34 | @Provides
35 | fun provideIoDispatcher(): CoroutineDispatcher {
36 | return Dispatchers.IO
37 | }
38 | }
--------------------------------------------------------------------------------
/feature/login/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Merhaba, Film Dünyasına Hoş Geldiniz!
4 | Şifre
5 | Giriş
6 | Şifremi Unuttum
7 | Şifre Sıfırlama
8 | Şifrenizi sıfırlamak için uygulamada oturum açarken kullandığınız e-posta adresini girin.
9 | E-mail Adresi
10 | Gönder
11 | İptal
12 | Hesabın yok mu ?
13 | Hesap Oluştur
14 | Or Continue With
15 | Parola sıfırlama maili gönderildi. Lütfen email kutunu kontrol et.
16 | İzleme listeniz yüklenemedi. Lütfen daha sonra tekrar deneyiniz.
17 | Şununla oturum açın
18 |
--------------------------------------------------------------------------------
/core/datastore/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | kotlin("kapt")
5 | }
6 |
7 | android {
8 | namespace = "com.ahmetocak.datastore"
9 | compileSdk = ConfigData.compileSdk
10 |
11 | defaultConfig {
12 | minSdk = ConfigData.minSdk
13 |
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = BuildTypes.isMinifyEnabled
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | implementation(libs.androidx.core.ktx)
39 |
40 | // Datastore
41 | implementation(libs.androidx.datastore.preferences)
42 |
43 | // Hilt
44 | implementation(libs.hilt.android)
45 | kapt(libs.hilt.android.compiler)
46 | }
--------------------------------------------------------------------------------
/core/authentication/src/main/java/com/ahmetocak/authentication/firebase/FirebaseAuthClient.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.authentication.firebase
2 |
3 | import com.google.android.gms.tasks.Task
4 | import com.google.firebase.auth.AuthResult
5 | import com.google.firebase.auth.EmailAuthProvider
6 | import com.google.firebase.auth.FirebaseAuth
7 | import javax.inject.Inject
8 |
9 | class FirebaseAuthClient @Inject constructor() {
10 |
11 | private val auth = FirebaseAuth.getInstance()
12 |
13 | fun signUp(email: String, password: String): Task =
14 | auth.createUserWithEmailAndPassword(email, password)
15 |
16 | fun signOut() = auth.signOut()
17 |
18 | fun login(email: String, password: String): Task =
19 | auth.signInWithEmailAndPassword(email, password)
20 |
21 | fun sendResetPasswordEmail(email: String): Task =
22 | auth.sendPasswordResetEmail(email)
23 |
24 | fun reAuthenticate(email: String, password: String): Task? {
25 | return auth.currentUser?.reauthenticate(
26 | EmailAuthProvider.getCredential(email, password)
27 | )
28 | }
29 |
30 | fun deleteAccount(): Task? = auth.currentUser?.delete()
31 |
32 | fun getUserEmail(): String? = auth.currentUser?.email
33 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/MovieRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.network.model.movie.NetworkActorDetails
5 | import com.ahmetocak.network.model.movie.NetworkActorMovies
6 | import com.ahmetocak.network.model.movie.NetworkMovie
7 | import com.ahmetocak.network.model.movie_detail.NetworkMovieCredit
8 | import com.ahmetocak.network.model.movie_detail.NetworkMovieDetail
9 | import com.ahmetocak.network.model.movie_detail.NetworkMovieTrailer
10 |
11 | interface MovieRemoteDataSource {
12 |
13 | suspend fun getTrendingMoviesFirstPage(): Response
14 |
15 | suspend fun getTopRatedMoviesFirstPage(): Response
16 |
17 | suspend fun getUpcomingMoviesFirstPage(): Response
18 |
19 | suspend fun getMovieDetails(movieId: Int): Response
20 |
21 | suspend fun getMovieCredits(movieId: Int): Response
22 |
23 | suspend fun getMovieTrailers(movieId: Int): Response
24 |
25 | suspend fun getActorDetails(actorId: Int): Response
26 |
27 | suspend fun getActorMovies(actorId: Int): Response
28 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/CircularProgressIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.CircularProgressIndicator
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 | import com.ahmetocak.designsystem.dimens.ComponentDimens
14 |
15 | @Composable
16 | fun ButtonCircularProgressIndicator() {
17 | CircularProgressIndicator(
18 | modifier = Modifier.size(ComponentDimens.buttonCircularProgressIndicatorSize),
19 | strokeWidth = 2.dp
20 | )
21 | }
22 |
23 | @Composable
24 | fun FullScreenCircularProgressIndicator(paddingValues: PaddingValues = PaddingValues(0.dp)) {
25 | Box(
26 | modifier = Modifier
27 | .fillMaxSize()
28 | .padding(paddingValues),
29 | contentAlignment = Alignment.Center
30 | ) {
31 | CircularProgressIndicator()
32 | }
33 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/dimens/AdaptiveModifiers.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.dimens
2 |
3 | import androidx.compose.foundation.layout.width
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.composed
6 | import androidx.compose.ui.platform.LocalConfiguration
7 | import androidx.compose.ui.unit.dp
8 | import com.ahmetocak.designsystem.WindowSizeClasses
9 | import com.ahmetocak.designsystem.computeWindowWidthSize
10 |
11 | fun Modifier.setAdaptiveWidth(
12 | compactWidthRatio: Int = 1,
13 | mediumWidthRatio: Int = 2,
14 | expandedWidthRatio: Int = 2
15 | ): Modifier = this.composed {
16 | computeWindowWidthSize()?.let { windowWidthSizeClasses ->
17 | when (windowWidthSizeClasses) {
18 | WindowSizeClasses.COMPACT -> {
19 | then(Modifier.width(LocalConfiguration.current.screenWidthDp.dp / compactWidthRatio))
20 | }
21 |
22 | WindowSizeClasses.MEDIUM -> {
23 | then(Modifier.width(LocalConfiguration.current.screenWidthDp.dp / mediumWidthRatio))
24 | }
25 |
26 | WindowSizeClasses.EXPANDED -> {
27 | then(Modifier.width(LocalConfiguration.current.screenWidthDp.dp / expandedWidthRatio))
28 | }
29 | }
30 | } ?: this
31 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/Scaffold.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.material3.FabPosition
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.Scaffold
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 |
11 | @Composable
12 | fun MovieScaffold(
13 | modifier: Modifier = Modifier,
14 | topBar: @Composable (() -> Unit) = {},
15 | bottomBar: @Composable (() -> Unit) = {},
16 | floatingActionButton: @Composable (() -> Unit) = {},
17 | floatingActionButtonPosition: FabPosition = FabPosition.End,
18 | backgroundColor: Color = MaterialTheme.colorScheme.background,
19 | contentColor: Color = MaterialTheme.colorScheme.onBackground,
20 | content: @Composable (PaddingValues) -> Unit
21 | ) {
22 | Scaffold(
23 | modifier = modifier,
24 | topBar = topBar,
25 | bottomBar = bottomBar,
26 | floatingActionButton = floatingActionButton,
27 | floatingActionButtonPosition = floatingActionButtonPosition,
28 | containerColor = backgroundColor,
29 | contentColor = contentColor,
30 | content = content
31 | )
32 | }
--------------------------------------------------------------------------------
/feature/login/src/main/res/drawable/ic_google.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/core/authentication/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | kotlin("kapt")
5 | }
6 |
7 | android {
8 | namespace = "com.ahmetocak.authentication"
9 | compileSdk = ConfigData.compileSdk
10 |
11 | defaultConfig {
12 | minSdk = ConfigData.minSdk
13 |
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = BuildTypes.isMinifyEnabled
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | implementation(libs.androidx.core.ktx)
39 |
40 | // Hilt
41 | implementation(libs.hilt.android)
42 | kapt(libs.hilt.android.compiler)
43 |
44 | // Firebase
45 | implementation(platform(libs.firebase.bom))
46 | implementation(libs.firebase.auth)
47 | implementation(libs.google.android.gms)
48 | }
--------------------------------------------------------------------------------
/feature/login/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Email
4 | Password
5 | E-mail Address
6 | Hello, Welcome to the Movie World!
7 | Login
8 | Forgot Password ?
9 | Don\'t have an account ?
10 | Sign Up
11 | Password Reset
12 | To reset your password, enter the email address you use to sign in to app.
13 | Send
14 | Cancel
15 | Password reset mail has been sent. Please check your email box.
16 | Please enter a valid email address
17 | Your watchlist could not be loaded. please try again later.
18 | Or Continue With
19 | Sign in With
20 |
--------------------------------------------------------------------------------
/core/common/src/main/java/com/ahmetocak/common/helpers/UiText.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.common.helpers
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.res.stringResource
7 |
8 | /**
9 | * Sealed class representing different types of UI text resources.
10 | */
11 | sealed class UiText {
12 | /**
13 | * Represents a dynamic string value.
14 | *
15 | * @param value The dynamic string value.
16 | */
17 | data class DynamicString(val value: String) : UiText()
18 |
19 | /**
20 | * Represents a string resource with optional format arguments.
21 | *
22 | * @param resId The resource ID of the string.
23 | * @param args Optional format arguments for the string resource.
24 | */
25 | class StringResource(
26 | @StringRes val resId: Int,
27 | vararg val args: Any
28 | ): UiText()
29 |
30 | @Composable
31 | fun asString(): String {
32 | return when(this) {
33 | is DynamicString -> value
34 | is StringResource -> stringResource(resId, *args)
35 | }
36 | }
37 |
38 | fun asString(context: Context): String {
39 | return when(this) {
40 | is DynamicString -> value
41 | is StringResource -> context.getString(resId, args)
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/CheckMovieInWatchListUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase
2 |
3 | import com.ahmetocak.common.helpers.UiText
4 | import com.ahmetocak.common.helpers.handleTaskError
5 | import com.ahmetocak.domain.firebase.firestore.GetMovieDataUseCase
6 | import com.ahmetocak.model.firebase.WatchList
7 | import javax.inject.Inject
8 |
9 | class CheckMovieInWatchListUseCase @Inject constructor(
10 | private val getMovieDataUseCase: GetMovieDataUseCase
11 | ) {
12 |
13 | operator fun invoke(
14 | movieId: Int,
15 | onError: (UiText) -> Unit,
16 | onSuccess: (Boolean) -> Unit
17 | ) {
18 | getMovieDataUseCase().addOnCompleteListener { task ->
19 | if (task.isSuccessful) {
20 | if (task.result != null && task.result.exists()) {
21 | val watchList = task.result.toObject(WatchList::class.java)?.watchList ?: emptyList()
22 |
23 | val movieInWatchList = watchList.firstOrNull { movie ->
24 | movie.id == movieId
25 | }
26 |
27 | onSuccess(movieInWatchList != null)
28 | } else {
29 | onSuccess(false)
30 | }
31 | } else {
32 | onError(handleTaskError(task.exception))
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/youtube_trailer_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
27 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/paging_source/MoviesPagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie.paging_source
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.ahmetocak.network.model.movie.NetworkMovie
6 | import com.ahmetocak.network.model.movie.NetworkMovieContent
7 |
8 | class MoviesPagingSource(
9 | private val apiCall: suspend (Int) -> NetworkMovie
10 | ) : PagingSource() {
11 |
12 | override suspend fun load(params: LoadParams): LoadResult {
13 | return try {
14 | val currentPageNumber = params.key ?: 1
15 | val response = apiCall(currentPageNumber)
16 | LoadResult.Page(
17 | data = response.movies,
18 | prevKey = null,
19 | nextKey = if (currentPageNumber < response.totalPages) currentPageNumber + 1 else null
20 | )
21 | } catch (e: Exception) {
22 | LoadResult.Error(e)
23 | }
24 | }
25 |
26 | override fun getRefreshKey(state: PagingState): Int? {
27 | return state.anchorPosition?.let { anchorPosition ->
28 | val anchorPage = state.closestPageToPosition(anchorPosition)
29 | anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/core/datastore/src/main/java/com/ahmetocak/datastore/di/DatastoreModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.datastore.di
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
6 | import androidx.datastore.preferences.core.Preferences
7 | import androidx.datastore.preferences.preferencesDataStoreFile
8 | import com.ahmetocak.datastore.datasource.MovieAppPreferenceDataSource
9 | import com.ahmetocak.datastore.datasource.MovieAppPreferenceDataSourceImpl
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.hilt.InstallIn
13 | import dagger.hilt.android.qualifiers.ApplicationContext
14 | import dagger.hilt.components.SingletonComponent
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object DatastoreModule {
20 |
21 | @Singleton
22 | @Provides
23 | fun provideDataStoreDataSource(datastore: DataStore): MovieAppPreferenceDataSource {
24 | return MovieAppPreferenceDataSourceImpl(datastore)
25 | }
26 |
27 | @Singleton
28 | @Provides
29 | fun provideDatastore(@ApplicationContext context: Context): DataStore {
30 | return PreferenceDataStoreFactory.create(
31 | produceFile = {
32 | context.preferencesDataStoreFile("app_preferences_datastore")
33 | }
34 | )
35 | }
36 | }
--------------------------------------------------------------------------------
/feature/movie_details/src/main/res/layout/youtube_trailer_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/core/database/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | kotlin("kapt")
5 | }
6 |
7 | android {
8 | namespace = "com.ahmetocak.database"
9 | compileSdk = ConfigData.compileSdk
10 |
11 | defaultConfig {
12 | minSdk = ConfigData.minSdk
13 |
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = BuildTypes.isMinifyEnabled
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | implementation(libs.androidx.core.ktx)
39 |
40 | testImplementation(libs.junit)
41 |
42 | // Hilt
43 | implementation(libs.hilt.android)
44 | kapt(libs.hilt.android.compiler)
45 |
46 | // Room
47 | implementation(libs.androidx.room.runtime)
48 | annotationProcessor(libs.androidx.room.compiler)
49 | kapt(libs.room.compiler)
50 | implementation(libs.androidx.room.ktx)
51 |
52 | implementation(project(":core:common"))
53 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/paging_source/SearchMoviesPagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie.paging_source
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.ahmetocak.network.api.MovieApi
6 | import com.ahmetocak.network.model.movie.NetworkMovieContent
7 |
8 | class SearchMoviesPagingSource(
9 | private val query: String,
10 | private val api: MovieApi
11 | ): PagingSource() {
12 |
13 | override suspend fun load(params: LoadParams): LoadResult {
14 | return try {
15 | val currentPageNumber = params.key ?: 1
16 | val response = api.searchMovie(query = query, page = currentPageNumber)
17 | LoadResult.Page(
18 | data = response.movies.filter { it.posterImagePath != null },
19 | prevKey = null,
20 | nextKey = if (currentPageNumber < response.totalPages) currentPageNumber + 1 else null
21 | )
22 | } catch (e: Exception) {
23 | LoadResult.Error(e)
24 | }
25 | }
26 |
27 | override fun getRefreshKey(state: PagingState): Int? {
28 | return state.anchorPosition?.let { anchorPosition ->
29 | val anchorPage = state.closestPageToPosition(anchorPosition)
30 | anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/navigation/NavigationBar.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components.navigation
2 |
3 | import androidx.compose.material3.Icon
4 | import androidx.compose.material3.NavigationBar
5 | import androidx.compose.material3.NavigationBarItem
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.res.stringResource
9 | import com.ahmetocak.navigation.HomeSections
10 |
11 | @Composable
12 | fun MovieNavigationBar(
13 | tabs: Array,
14 | currentRoute: String,
15 | navigateToRoute: (String) -> Unit
16 | ) {
17 | val currentSection = tabs.first { it.route == currentRoute }
18 |
19 | NavigationBar {
20 | tabs.forEach { section ->
21 | NavigationBarItem(
22 | selected = currentSection.route == section.route,
23 | onClick = {
24 | navigateToRoute(section.route)
25 | },
26 | icon = {
27 | Icon(
28 | imageVector = if (currentSection.route == section.route)
29 | section.selectedIcon else section.unSelectedIcon,
30 | contentDescription = null
31 | )
32 | },
33 | label = {
34 | Text(text = stringResource(id = section.title))
35 | }
36 | )
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/paging_source/UserMovieReviewsPagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie.paging_source
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.ahmetocak.network.api.MovieApi
6 | import com.ahmetocak.network.model.movie.NetworkUserReviewResults
7 |
8 | class UserMovieReviewsPagingSource(
9 | private val movieId: Int,
10 | private val api: MovieApi
11 | ): PagingSource() {
12 |
13 | override suspend fun load(params: LoadParams): LoadResult {
14 | return try {
15 | val currentPageNumber = params.key ?: 1
16 | val response = api.getUserMovieReviews(movieId = movieId, page = currentPageNumber)
17 | LoadResult.Page(
18 | data = response.results,
19 | prevKey = null,
20 | nextKey = if (currentPageNumber < response.totalPages) currentPageNumber + 1 else null
21 | )
22 | } catch (e: Exception) {
23 | LoadResult.Error(e)
24 | }
25 | }
26 |
27 | override fun getRefreshKey(state: PagingState): Int? {
28 | return state.anchorPosition?.let { anchorPosition ->
29 | val anchorPage = state.closestPageToPosition(anchorPosition)
30 | anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/paging_source/RecommendedMoviesPagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie.paging_source
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.ahmetocak.network.api.MovieApi
6 | import com.ahmetocak.network.model.movie.NetworkRecommendedMovieContent
7 |
8 | class RecommendedMoviesPagingSource(
9 | private val movieId: Int,
10 | private val api: MovieApi
11 | ): PagingSource() {
12 |
13 | override suspend fun load(params: LoadParams): LoadResult {
14 | return try {
15 | val currentPageNumber = params.key ?: 1
16 | val response = api.getMovieRecommendations(movieId = movieId, page = currentPageNumber)
17 | LoadResult.Page(
18 | data = response.recommendations.filter { it.image != null },
19 | prevKey = null,
20 | nextKey = if (currentPageNumber < response.totalPages) currentPageNumber + 1 else null
21 | )
22 | } catch (e: Exception) {
23 | LoadResult.Error(e)
24 | }
25 | }
26 |
27 | override fun getRefreshKey(state: PagingState): Int? {
28 | return state.anchorPosition?.let { anchorPosition ->
29 | val anchorPage = state.closestPageToPosition(anchorPosition)
30 | anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/SignInWithGoogleUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import android.content.Intent
4 | import androidx.activity.result.IntentSenderRequest
5 | import com.ahmetocak.authentication.firebase.GoogleAuthClient
6 | import com.ahmetocak.common.helpers.UiText
7 | import com.ahmetocak.common.helpers.handleTaskError
8 | import javax.inject.Inject
9 |
10 | class SignInWithGoogleUseCase @Inject constructor(private val googleAuthClient: GoogleAuthClient) {
11 |
12 | fun startSignInIntent(
13 | onTaskFailed: (UiText) -> Unit,
14 | onTaskSuccess: (IntentSenderRequest) -> Unit
15 | ) {
16 | googleAuthClient.startSignInIntent().addOnCompleteListener { task ->
17 | if (task.isSuccessful) {
18 | onTaskSuccess(
19 | IntentSenderRequest.Builder(
20 | task.result.pendingIntent.intentSender
21 | ).build()
22 | )
23 | } else {
24 | onTaskFailed(handleTaskError(task.exception))
25 | }
26 | }
27 | }
28 |
29 | fun signInWithGoogle(
30 | intent: Intent,
31 | onTaskSuccess: () -> Unit,
32 | onTaskFailed: (UiText) -> Unit
33 | ) {
34 | googleAuthClient.signInWithIntent(intent).addOnCompleteListener { task ->
35 | if (task.isSuccessful) {
36 | onTaskSuccess()
37 | } else {
38 | onTaskFailed(handleTaskError(task.exception))
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/firebase/storage/FirebaseStorageDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.firebase.storage
2 |
3 | import android.net.Uri
4 | import com.ahmetocak.network.helpers.FirebaseConstants
5 | import com.google.android.gms.tasks.Task
6 | import com.google.firebase.auth.FirebaseAuth
7 | import com.google.firebase.storage.FirebaseStorage
8 | import com.google.firebase.storage.UploadTask
9 | import javax.inject.Inject
10 |
11 | class FirebaseStorageDataSourceImpl @Inject constructor(
12 | firebaseStorage: FirebaseStorage,
13 | private val firebaseAuth: FirebaseAuth
14 | ) : FirebaseStorageDataSource {
15 |
16 | private val storageRef = firebaseStorage.reference
17 |
18 | override fun uploadProfileImage(imageUri: Uri): UploadTask {
19 | val profileImageRef =
20 | storageRef.child("${FirebaseConstants.Firestorage.USER_PROFILE_IMG_DIRECTORY_NAME}/${firebaseAuth.currentUser?.uid}")
21 | return profileImageRef.putFile(imageUri)
22 | }
23 |
24 | override fun getUserProfileImage(): Task {
25 | val profileImageRef =
26 | storageRef.child("${FirebaseConstants.Firestorage.USER_PROFILE_IMG_DIRECTORY_NAME}/${firebaseAuth.currentUser?.uid}")
27 | return profileImageRef.downloadUrl
28 | }
29 |
30 | override fun deleteUserProfileImage(): Task {
31 | val profileImageRef =
32 | storageRef.child("${FirebaseConstants.Firestorage.USER_PROFILE_IMG_DIRECTORY_NAME}/${firebaseAuth.currentUser?.uid}")
33 | return profileImageRef.delete()
34 | }
35 | }
--------------------------------------------------------------------------------
/core/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | kotlin("kapt")
5 | }
6 |
7 | android {
8 | namespace = "com.ahmetocak.data"
9 | compileSdk = ConfigData.compileSdk
10 |
11 | defaultConfig {
12 | minSdk = ConfigData.minSdk
13 |
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = BuildTypes.isMinifyEnabled
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | implementation(libs.androidx.core.ktx)
39 |
40 | // Hilt
41 | implementation(libs.hilt.android)
42 | kapt(libs.hilt.android.compiler)
43 |
44 | // Paging 3
45 | implementation(libs.androidx.paging.runtime.ktx)
46 |
47 | // Firebase
48 | implementation(platform(libs.firebase.bom))
49 | implementation(libs.firebase.firestore)
50 | implementation(libs.firebase.storage)
51 |
52 | implementation(project(":core:network"))
53 | implementation(project(":core:database"))
54 | implementation(project(":core:datastore"))
55 | implementation(project(":core:model"))
56 | implementation(project(":core:common"))
57 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/ErrorView.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.Error
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.text.style.TextAlign
16 | import androidx.compose.ui.unit.Dp
17 | import com.ahmetocak.designsystem.dimens.ComponentDimens
18 | import com.ahmetocak.designsystem.dimens.Dimens
19 |
20 | @Composable
21 | fun ErrorView(
22 | modifier: Modifier = Modifier,
23 | iconSize: Dp = ComponentDimens.errorViewIconSize,
24 | errorMessage: String
25 | ) {
26 | Column(
27 | modifier = modifier,
28 | verticalArrangement = Arrangement.Center,
29 | horizontalAlignment = Alignment.CenterHorizontally
30 | ) {
31 | Icon(
32 | modifier = Modifier.size(iconSize),
33 | imageVector = Icons.Filled.Error,
34 | contentDescription = null,
35 | tint = MaterialTheme.colorScheme.error
36 | )
37 | Text(
38 | modifier = Modifier.padding(top = Dimens.twoLevelPadding),
39 | text = errorMessage,
40 | textAlign = TextAlign.Center
41 | )
42 | }
43 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/helpers/NetworkConstants.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.helpers
2 |
3 | object NetworkConstants {
4 | const val BASE_URL = "https://api.themoviedb.org/"
5 | object EndPoints {
6 | const val TRENDING = "/3/trending/movie/{${Paths.TIME}}"
7 | const val TOP_RATED = "/3/movie/top_rated"
8 | const val UPCOMING = "/3/movie/upcoming"
9 | const val MOVIE_DETAILS = "/3/movie/{${Paths.MOVIE_ID}}"
10 | const val MOVIE_CREDITS = "/3/movie/{${Paths.MOVIE_ID}}/credits"
11 | const val MOVIE_TRAILERS = "/3/movie/{${Paths.MOVIE_ID}}/videos"
12 | const val SEARCH_MOVIE = "/3/search/movie"
13 | const val ACTOR_DETAILS = "/3/person/{${Paths.ACTOR_ID}}"
14 | const val ACTOR_MOVIES = "/3/person/{${Paths.ACTOR_ID}}/movie_credits"
15 | const val USER_REVIEWS = "/3/movie/{${Paths.MOVIE_ID}}/reviews"
16 | const val RECOMMENDATIONS = "/3/movie/{${Paths.MOVIE_ID}}/recommendations"
17 | }
18 |
19 | object Queries {
20 | const val PAGE = "page"
21 | const val API_KEY = "api_key"
22 | const val SEARCH_QUERY = "query"
23 | const val LANGUAGE = "language"
24 | }
25 |
26 | object Paths {
27 | const val MOVIE_ID = "movie_id"
28 | const val ACTOR_ID = "actor_id"
29 | const val TIME = "time_window"
30 | }
31 | }
32 |
33 | object FirebaseConstants {
34 | object Firestore {
35 | const val WATCH_LIST_COLLECTION_NAME = "movie_watch_lists"
36 | const val WATCH_LIST_ARRAY_NAME = "watchList"
37 | }
38 |
39 | object Firestorage {
40 | const val USER_PROFILE_IMG_DIRECTORY_NAME = "user_profile_images"
41 | }
42 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/navigation/NavigationRail.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components.navigation
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.offset
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.NavigationRail
7 | import androidx.compose.material3.NavigationRailItem
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.res.stringResource
12 | import androidx.compose.ui.unit.dp
13 | import com.ahmetocak.navigation.HomeSections
14 |
15 | @Composable
16 | fun MovieNavigationRail(
17 | tabs: Array,
18 | navigateToRoute: (String) -> Unit,
19 | currentRoute: String
20 | ) {
21 | NavigationRail(modifier = Modifier.offset(x = (-1).dp)) {
22 | Spacer(Modifier.weight(1f))
23 | tabs.forEachIndexed { _, section ->
24 | NavigationRailItem(
25 | selected = currentRoute == section.route,
26 | onClick = {
27 | navigateToRoute(section.route)
28 | },
29 | icon = {
30 | Icon(
31 | imageVector = if (currentRoute == section.route)
32 | section.selectedIcon else section.unSelectedIcon,
33 | contentDescription = null
34 | )
35 | },
36 | label = {
37 | Text(text = stringResource(id = section.title))
38 | }
39 | )
40 | }
41 | Spacer(Modifier.weight(1f))
42 | }
43 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/TextButton.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.material3.ButtonColors
7 | import androidx.compose.material3.ButtonDefaults
8 | import androidx.compose.material3.ButtonElevation
9 | import androidx.compose.material3.Text
10 | import androidx.compose.material3.TextButton
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Shape
15 | import androidx.compose.ui.text.font.FontWeight
16 |
17 | @Composable
18 | fun MovieTextButton(
19 | modifier: Modifier = Modifier,
20 | text: String,
21 | onClick: () -> Unit,
22 | fontWeight: FontWeight = FontWeight.W700,
23 | enabled: Boolean = true,
24 | shape: Shape = ButtonDefaults.textShape,
25 | colors: ButtonColors = ButtonDefaults.textButtonColors(),
26 | elevation: ButtonElevation? = null,
27 | border: BorderStroke? = null,
28 | contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding,
29 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
30 | ) {
31 | TextButton(
32 | modifier = modifier,
33 | onClick = onClick,
34 | contentPadding = contentPadding,
35 | enabled = enabled,
36 | shape = shape,
37 | colors = colors,
38 | elevation = elevation,
39 | border = border,
40 | interactionSource = interactionSource
41 | ) {
42 | Text(text = text, fontWeight = fontWeight)
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/di/DatasourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.di
2 |
3 | import com.ahmetocak.network.api.MovieApi
4 | import com.ahmetocak.network.datasource.firebase.firestore.FirebaseFirestoreDataSource
5 | import com.ahmetocak.network.datasource.firebase.firestore.FirebaseFirestoreDataSourceImpl
6 | import com.ahmetocak.network.datasource.firebase.storage.FirebaseStorageDataSource
7 | import com.ahmetocak.network.datasource.firebase.storage.FirebaseStorageDataSourceImpl
8 | import com.ahmetocak.network.datasource.movie.MovieRemoteDataSource
9 | import com.ahmetocak.network.datasource.movie.MovieRemoteDataSourceImpl
10 | import com.google.firebase.auth.FirebaseAuth
11 | import com.google.firebase.firestore.FirebaseFirestore
12 | import com.google.firebase.storage.FirebaseStorage
13 | import dagger.Module
14 | import dagger.Provides
15 | import dagger.hilt.InstallIn
16 | import dagger.hilt.components.SingletonComponent
17 | import javax.inject.Singleton
18 |
19 | @Module
20 | @InstallIn(SingletonComponent::class)
21 | object DataSourceModule {
22 |
23 | @Singleton
24 | @Provides
25 | fun provideMovieRemoteDataSource(api: MovieApi): MovieRemoteDataSource {
26 | return MovieRemoteDataSourceImpl(api)
27 | }
28 |
29 | @Singleton
30 | @Provides
31 | fun provideFirebaseFirestoreDataSource(
32 | firestore: FirebaseFirestore,
33 | auth: FirebaseAuth
34 | ): FirebaseFirestoreDataSource {
35 | return FirebaseFirestoreDataSourceImpl(firestore, auth)
36 | }
37 |
38 | @Singleton
39 | @Provides
40 | fun provideFirebaseStorageDataSource(
41 | storage: FirebaseStorage,
42 | auth: FirebaseAuth
43 | ): FirebaseStorageDataSource {
44 | return FirebaseStorageDataSourceImpl(storage, auth)
45 | }
46 | }
--------------------------------------------------------------------------------
/core/data/src/main/java/com/ahmetocak/data/repository/firebase/FirebaseRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.data.repository.firebase
2 |
3 | import android.net.Uri
4 | import com.ahmetocak.data.mapper.toNetworkWatchListMovie
5 | import com.ahmetocak.model.firebase.WatchListMovie
6 | import com.ahmetocak.network.datasource.firebase.firestore.FirebaseFirestoreDataSource
7 | import com.ahmetocak.network.datasource.firebase.storage.FirebaseStorageDataSource
8 | import com.google.android.gms.tasks.Task
9 | import com.google.firebase.firestore.DocumentSnapshot
10 | import com.google.firebase.storage.UploadTask
11 | import javax.inject.Inject
12 |
13 | class FirebaseRepositoryImpl @Inject constructor(
14 | private val firebaseFirestoreDataSource: FirebaseFirestoreDataSource,
15 | private val firebaseStorageDataSource: FirebaseStorageDataSource
16 | ) : FirebaseRepository {
17 |
18 | override fun addMovieToFirestore(watchListMovie: WatchListMovie): Task =
19 | firebaseFirestoreDataSource.addMovieData(watchListMovie.toNetworkWatchListMovie())
20 |
21 | override fun updateMovieData(watchListMovie: List): Task =
22 | firebaseFirestoreDataSource.updateMovieData(watchListMovie.toNetworkWatchListMovie())
23 |
24 | override fun getMovieData(): Task = firebaseFirestoreDataSource.getMovieData()
25 |
26 | override fun deleteMovieDocument(): Task =
27 | firebaseFirestoreDataSource.deleteMovieDocument()
28 |
29 | override fun uploadProfileImage(imageUri: Uri): UploadTask =
30 | firebaseStorageDataSource.uploadProfileImage(imageUri)
31 |
32 | override fun getUserProfileImage(): Task = firebaseStorageDataSource.getUserProfileImage()
33 |
34 | override fun deleteUserProfileImage(): Task =
35 | firebaseStorageDataSource.deleteUserProfileImage()
36 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/WindowSize.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem
2 |
3 | import android.app.Activity
4 | import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
5 | import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
6 | import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
7 | import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.platform.LocalContext
10 | import com.ahmetocak.common.findActivity
11 |
12 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
13 | @Composable
14 | fun computeWindowWidthSize(activity: Activity? = LocalContext.current.findActivity()): WindowSizeClasses? {
15 | return when (activity?.let { calculateWindowSizeClass(activity = it).widthSizeClass }) {
16 | WindowWidthSizeClass.Compact -> WindowSizeClasses.COMPACT
17 |
18 | WindowWidthSizeClass.Medium -> WindowSizeClasses.MEDIUM
19 |
20 | WindowWidthSizeClass.Expanded -> WindowSizeClasses.EXPANDED
21 |
22 | else -> null
23 | }
24 | }
25 |
26 | @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
27 | @Composable
28 | fun computeWindowHeightSize(activity: Activity? = LocalContext.current.findActivity()): WindowSizeClasses? {
29 | return when (activity?.let { calculateWindowSizeClass(activity = it).heightSizeClass }) {
30 | WindowHeightSizeClass.Compact -> WindowSizeClasses.COMPACT
31 |
32 | WindowHeightSizeClass.Medium -> WindowSizeClasses.MEDIUM
33 |
34 | WindowHeightSizeClass.Expanded -> WindowSizeClasses.EXPANDED
35 |
36 | else -> null
37 | }
38 | }
39 |
40 | sealed interface WindowSizeClasses {
41 | data object COMPACT : WindowSizeClasses
42 | data object MEDIUM : WindowSizeClasses
43 | data object EXPANDED : WindowSizeClasses
44 | }
--------------------------------------------------------------------------------
/core/navigation/src/main/java/com/ahmetocak/navigation/Routes.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.navigation
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.AccountCircle
6 | import androidx.compose.material.icons.filled.Bookmark
7 | import androidx.compose.material.icons.filled.Movie
8 | import androidx.compose.material.icons.filled.Search
9 | import androidx.compose.material.icons.outlined.AccountCircle
10 | import androidx.compose.material.icons.outlined.BookmarkBorder
11 | import androidx.compose.material.icons.outlined.Movie
12 | import androidx.compose.material.icons.outlined.Search
13 | import androidx.compose.ui.graphics.vector.ImageVector
14 |
15 | object MainDestinations {
16 | const val LOGIN_ROUTE = "login"
17 | const val SIGN_UP_ROUTE = "signUp"
18 | const val MOVIE_DETAILS_ROUTE = "movieDetails"
19 | const val MOVIE_DETAILS_ID_KEY = "movieId"
20 | const val SEE_ALL_ROUTE = "seeAll"
21 | const val SEE_ALL_TYPE_KEY = "seeAllType"
22 | const val HOME_ROUTE = "home"
23 | const val ACTOR_DETAILS_ROUTE = "actorDetails"
24 | const val ACTOR_DETAILS_ID_KEY = "actorId"
25 | }
26 |
27 | enum class HomeSections(
28 | @StringRes val title: Int,
29 | val selectedIcon: ImageVector,
30 | val unSelectedIcon: ImageVector,
31 | val route: String
32 | ) {
33 | MOVIES(R.string.movies_text, Icons.Filled.Movie, Icons.Outlined.Movie, "home/movies"),
34 | SEARCH(R.string.search_text, Icons.Filled.Search, Icons.Outlined.Search, "home/search"),
35 | WATCH_LIST(
36 | R.string.watch_list_text,
37 | Icons.Filled.Bookmark,
38 | Icons.Outlined.BookmarkBorder,
39 | "home/watch_list"
40 | ),
41 | PROFILE(
42 | R.string.profile_text,
43 | Icons.Filled.AccountCircle,
44 | Icons.Outlined.AccountCircle,
45 | "home/profile"
46 | )
47 | }
--------------------------------------------------------------------------------
/core/datastore/src/main/java/com/ahmetocak/datastore/datasource/MovieAppPreferenceDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.datastore.datasource
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import androidx.datastore.preferences.core.booleanPreferencesKey
6 | import androidx.datastore.preferences.core.edit
7 | import com.ahmetocak.datastore.DataStoreConstants
8 | import com.ahmetocak.datastore.datasource.MovieAppPreferenceDataSourceImpl.PreferencesKeys.APP_THEME
9 | import com.ahmetocak.datastore.datasource.MovieAppPreferenceDataSourceImpl.PreferencesKeys.DYNAMIC_COLOR
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.map
12 | import javax.inject.Inject
13 |
14 | class MovieAppPreferenceDataSourceImpl @Inject constructor(
15 | private val datastore: DataStore
16 | ): MovieAppPreferenceDataSource {
17 |
18 | private object PreferencesKeys {
19 | val APP_THEME = booleanPreferencesKey(DataStoreConstants.APP_THEME_KEY)
20 | val DYNAMIC_COLOR = booleanPreferencesKey(DataStoreConstants.DYNAMIC_COLOR_KEY)
21 | }
22 |
23 | override suspend fun observeAppTheme(): Flow {
24 | return datastore.data.map { preferences ->
25 | preferences[APP_THEME] ?: true
26 | }
27 | }
28 |
29 | override suspend fun updateAppTheme(darkMode: Boolean) {
30 | datastore.edit { preferences ->
31 | preferences[APP_THEME] = darkMode
32 | }
33 | }
34 |
35 | override suspend fun observeDynamicColor(): Flow {
36 | return datastore.data.map { preferences ->
37 | preferences[DYNAMIC_COLOR] ?: false
38 | }
39 | }
40 |
41 | override suspend fun updateDynamicColor(dynamicColor: Boolean) {
42 | datastore.edit { preferences ->
43 | preferences[DYNAMIC_COLOR] = dynamicColor
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/core/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.ahmetocak.navigation"
8 | compileSdk = ConfigData.compileSdk
9 |
10 | defaultConfig {
11 | minSdk = ConfigData.minSdk
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | }
16 |
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = BuildTypes.isMinifyEnabled
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = "1.8"
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation(libs.androidx.core.ktx)
38 | implementation(libs.androidx.lifecycle.runtime.ktx)
39 | implementation(libs.androidx.activity.compose)
40 | implementation(platform(libs.androidx.compose.bom))
41 | implementation(libs.androidx.compose.ui)
42 | implementation(libs.androidx.compose.ui.graphics)
43 | implementation(libs.androidx.compose.ui.preview)
44 | implementation(libs.androidx.compose.material3)
45 |
46 | testImplementation(libs.junit)
47 | androidTestImplementation(libs.androidx.junit)
48 | androidTestImplementation(libs.androidx.espresso.core)
49 | androidTestImplementation(platform(libs.androidx.compose.bom))
50 | androidTestImplementation(libs.androidx.compose.ui.test.junit4)
51 | debugImplementation(libs.androidx.compose.ui.tooling)
52 | debugImplementation(libs.androidx.compose.ui.test.manifest)
53 |
54 | // Material Icons Extended
55 | implementation(libs.androidx.compose.icons.extended)
56 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/movie/MovieRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.movie
2 |
3 | import com.ahmetocak.common.helpers.Response
4 | import com.ahmetocak.network.api.MovieApi
5 | import com.ahmetocak.network.model.movie.NetworkActorDetails
6 | import com.ahmetocak.network.model.movie.NetworkActorMovies
7 | import com.ahmetocak.network.helpers.apiCall
8 | import com.ahmetocak.network.model.movie.NetworkMovie
9 | import com.ahmetocak.network.model.movie_detail.NetworkMovieCredit
10 | import com.ahmetocak.network.model.movie_detail.NetworkMovieDetail
11 | import com.ahmetocak.network.model.movie_detail.NetworkMovieTrailer
12 | import javax.inject.Inject
13 |
14 | class MovieRemoteDataSourceImpl @Inject constructor(
15 | private val api: MovieApi
16 | ) : MovieRemoteDataSource {
17 |
18 | override suspend fun getTrendingMoviesFirstPage(): Response =
19 | apiCall { api.getTrendingMovies() }
20 |
21 | override suspend fun getTopRatedMoviesFirstPage(): Response =
22 | apiCall { api.getTopRatedMovies() }
23 |
24 | override suspend fun getUpcomingMoviesFirstPage(): Response =
25 | apiCall { api.getUpcomingMovies() }
26 |
27 | override suspend fun getMovieDetails(movieId: Int): Response =
28 | apiCall { api.getMovieDetails(movieId) }
29 |
30 | override suspend fun getMovieCredits(movieId: Int): Response =
31 | apiCall { api.getMovieCredits(movieId) }
32 |
33 | override suspend fun getMovieTrailers(movieId: Int): Response =
34 | apiCall { api.getMovieTrailers(movieId) }
35 |
36 | override suspend fun getActorDetails(actorId: Int): Response =
37 | apiCall { api.getActorDetails(actorId) }
38 |
39 | override suspend fun getActorMovies(actorId: Int): Response =
40 | apiCall { api.getActorMovies(actorId) }
41 | }
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/Button.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.ButtonColors
8 | import androidx.compose.material3.ButtonDefaults
9 | import androidx.compose.material3.ButtonElevation
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Shape
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.unit.TextUnit
17 | import androidx.compose.ui.unit.sp
18 |
19 | @Composable
20 | fun MovieButton(
21 | modifier: Modifier = Modifier,
22 | text: String,
23 | onClick: () -> Unit,
24 | fontWeight: FontWeight = FontWeight.W700,
25 | fontSize: TextUnit = 18.sp,
26 | enabled: Boolean = true,
27 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
28 | shape: Shape = ButtonDefaults.shape,
29 | colors: ButtonColors = ButtonDefaults.buttonColors(),
30 | elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
31 | border: BorderStroke? = null,
32 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
33 | ) {
34 | Button(
35 | modifier = modifier,
36 | onClick = onClick,
37 | enabled = enabled,
38 | contentPadding = contentPadding,
39 | shape = shape,
40 | colors = colors,
41 | elevation = elevation,
42 | border = border,
43 | interactionSource = interactionSource
44 | ) {
45 | Text(
46 | text = text,
47 | fontWeight = fontWeight,
48 | fontSize = fontSize
49 | )
50 | }
51 | }
--------------------------------------------------------------------------------
/core/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.util.Properties
2 |
3 | plugins {
4 | alias(libs.plugins.androidLibrary)
5 | alias(libs.plugins.kotlin.android)
6 | kotlin("kapt")
7 | }
8 |
9 | android {
10 | namespace = "com.ahmetocak.domain"
11 | compileSdk = ConfigData.compileSdk
12 |
13 | val p = Properties()
14 | p.load(project.rootProject.file("local.properties").inputStream())
15 |
16 | defaultConfig {
17 | minSdk = ConfigData.minSdk
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles("consumer-rules.pro")
21 |
22 | buildConfigField("String","GEMINI_API_KEY", p.getProperty("GEMINI_API_KEY"))
23 | }
24 |
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = BuildTypes.isMinifyEnabled
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | }
33 | }
34 | compileOptions {
35 | sourceCompatibility = JavaVersion.VERSION_17
36 | targetCompatibility = JavaVersion.VERSION_17
37 | }
38 | kotlinOptions {
39 | jvmTarget = "17"
40 | }
41 | buildFeatures {
42 | buildConfig = true
43 | }
44 | }
45 |
46 | dependencies {
47 |
48 | implementation(libs.androidx.core.ktx)
49 | implementation(libs.javax.inject)
50 |
51 | // Firebase
52 | implementation(platform(libs.firebase.bom))
53 | implementation(libs.firebase.auth)
54 | implementation(libs.firebase.firestore)
55 | implementation(libs.firebase.storage)
56 | implementation(libs.google.android.gms)
57 |
58 | //Google AI client SDK for Android
59 | implementation(libs.google.ai.client.generativeai)
60 |
61 | implementation(project(":core:model"))
62 | implementation(project(":core:data"))
63 | implementation(project(":core:common"))
64 | implementation(project(":core:authentication"))
65 | }
--------------------------------------------------------------------------------
/core/network/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.util.Properties
2 |
3 | plugins {
4 | alias(libs.plugins.androidLibrary)
5 | alias(libs.plugins.kotlin.android)
6 | kotlin("kapt")
7 | }
8 |
9 | android {
10 | namespace = "com.ahmetocak.network"
11 | compileSdk = ConfigData.compileSdk
12 |
13 | val p = Properties()
14 | p.load(project.rootProject.file("local.properties").inputStream())
15 |
16 | defaultConfig {
17 | minSdk = ConfigData.minSdk
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles("consumer-rules.pro")
21 |
22 | buildConfigField("String", "API_KEY", p.getProperty("API_KEY"))
23 | }
24 |
25 | buildTypes {
26 | release {
27 | isMinifyEnabled = BuildTypes.isMinifyEnabled
28 | proguardFiles(
29 | getDefaultProguardFile("proguard-android-optimize.txt"),
30 | "proguard-rules.pro"
31 | )
32 | }
33 | }
34 | compileOptions {
35 | sourceCompatibility = JavaVersion.VERSION_1_8
36 | targetCompatibility = JavaVersion.VERSION_1_8
37 | }
38 | kotlinOptions {
39 | jvmTarget = "1.8"
40 | }
41 | buildFeatures {
42 | buildConfig = true
43 | }
44 | }
45 |
46 | dependencies {
47 |
48 | implementation(libs.androidx.core.ktx)
49 |
50 | // Retrofit
51 | implementation(libs.retrofit)
52 | implementation(libs.converter.gson)
53 |
54 | // Hilt
55 | implementation(libs.hilt.android)
56 | kapt(libs.hilt.android.compiler)
57 |
58 | // Okhttp
59 | implementation(libs.okhttp)
60 |
61 | // Paging 3
62 | implementation(libs.androidx.paging.runtime.ktx)
63 |
64 | // Firebase
65 | implementation(platform(libs.firebase.bom))
66 | implementation(libs.firebase.auth)
67 | implementation(libs.firebase.firestore)
68 | implementation(libs.firebase.storage)
69 |
70 | implementation(project(":core:common"))
71 | }
--------------------------------------------------------------------------------
/core/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.ahmetocak.common"
8 | compileSdk = ConfigData.compileSdk
9 |
10 | defaultConfig {
11 | minSdk = ConfigData.minSdk
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | }
16 |
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = BuildTypes.isMinifyEnabled
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = "1.8"
32 | }
33 | buildFeatures {
34 | compose = true
35 | }
36 | composeOptions {
37 | kotlinCompilerExtensionVersion = ComposeOptions.kotlinCompilerExtensionVersion
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation(libs.androidx.core.ktx)
44 | implementation(libs.androidx.lifecycle.runtime.ktx)
45 | implementation(libs.androidx.activity.compose)
46 | implementation(platform(libs.androidx.compose.bom))
47 | implementation(libs.androidx.compose.ui)
48 | implementation(libs.androidx.compose.ui.graphics)
49 | implementation(libs.androidx.compose.ui.preview)
50 | implementation(libs.androidx.compose.material3)
51 |
52 | testImplementation(libs.junit)
53 | androidTestImplementation(libs.androidx.junit)
54 | androidTestImplementation(libs.androidx.espresso.core)
55 | androidTestImplementation(platform(libs.androidx.compose.bom))
56 | androidTestImplementation(libs.androidx.compose.ui.test.junit4)
57 | debugImplementation(libs.androidx.compose.ui.tooling)
58 | debugImplementation(libs.androidx.compose.ui.test.manifest)
59 |
60 | implementation(libs.javax.inject)
61 | }
--------------------------------------------------------------------------------
/feature/search/src/main/java/com/ahmetocak/search/SearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.search
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import androidx.paging.PagingData
9 | import androidx.paging.cachedIn
10 | import com.ahmetocak.common.helpers.UiText
11 | import com.ahmetocak.domain.movie.SearchMovieUseCase
12 | import com.ahmetocak.model.movie.MovieContent
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import kotlinx.coroutines.flow.Flow
15 | import kotlinx.coroutines.flow.MutableStateFlow
16 | import kotlinx.coroutines.flow.StateFlow
17 | import kotlinx.coroutines.flow.asStateFlow
18 | import kotlinx.coroutines.flow.emptyFlow
19 | import kotlinx.coroutines.flow.update
20 | import javax.inject.Inject
21 |
22 | @HiltViewModel
23 | class SearchViewModel @Inject constructor(
24 | private val searchMovieUseCase: SearchMovieUseCase
25 | ) : ViewModel() {
26 |
27 | private val _uiState = MutableStateFlow(SearchUiState())
28 | val uiState: StateFlow = _uiState.asStateFlow()
29 |
30 | var query by mutableStateOf("")
31 | private set
32 |
33 | fun updateQueryValue(value: String) {
34 | query = value
35 | }
36 |
37 | fun searchMovie() {
38 | if (query.isNotBlank()) {
39 | _uiState.update {
40 | it.copy(
41 | queryFieldErrorMessage = null,
42 | isSearchDone = true,
43 | searchResult = searchMovieUseCase(query).cachedIn(viewModelScope)
44 | )
45 | }
46 | } else {
47 | _uiState.update {
48 | it.copy(queryFieldErrorMessage = UiText.StringResource(R.string.blank_field))
49 | }
50 | }
51 | }
52 | }
53 |
54 | data class SearchUiState(
55 | val isSearchDone: Boolean = false,
56 | val searchResult: Flow> = emptyFlow(),
57 | val queryFieldErrorMessage: UiText? = null
58 | )
--------------------------------------------------------------------------------
/core/designsystem/src/main/java/com/ahmetocak/designsystem/components/DynamicLayout.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.designsystem.components
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.platform.LocalConfiguration
11 |
12 | /**
13 | * A composable function to dynamically adjust the layout based on screen orientation.
14 | * In portrait mode, it arranges the content in a column, while in landscape mode, it arranges
15 | * the content in a row.
16 | *
17 | * @param modifier the modifier for the layout
18 | * @param horizontalAlignment the horizontal alignment of the content
19 | * @param horizontalArrangement the horizontal arrangement of the content
20 | * @param verticalAlignment the vertical alignment of the content
21 | * @param verticalArrangement the vertical arrangement of the content
22 | * @param content the composable content to be displayed within the layout
23 | */
24 | @Composable
25 | inline fun DynamicLayout(
26 | modifier: Modifier = Modifier,
27 | horizontalAlignment: Alignment.Horizontal = Alignment.Start,
28 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
29 | verticalAlignment: Alignment.Vertical = Alignment.Top,
30 | verticalArrangement: Arrangement.Vertical = Arrangement.Top,
31 | content: @Composable () -> Unit
32 | ) {
33 | if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
34 | Column(
35 | modifier = modifier,
36 | horizontalAlignment = horizontalAlignment,
37 | verticalArrangement = verticalArrangement
38 | ) {
39 | content()
40 | }
41 | } else {
42 | Row(
43 | modifier = modifier,
44 | horizontalArrangement = horizontalArrangement,
45 | verticalAlignment = verticalAlignment
46 | ) {
47 | content()
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/core/ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.ahmetocak.ui"
8 | compileSdk = ConfigData.compileSdk
9 |
10 | defaultConfig {
11 | minSdk = ConfigData.minSdk
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | }
16 |
17 | buildTypes {
18 | release {
19 | isMinifyEnabled = BuildTypes.isMinifyEnabled
20 | proguardFiles(
21 | getDefaultProguardFile("proguard-android-optimize.txt"),
22 | "proguard-rules.pro"
23 | )
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = "1.8"
32 | }
33 | buildFeatures {
34 | compose = true
35 | }
36 | composeOptions {
37 | kotlinCompilerExtensionVersion = ComposeOptions.kotlinCompilerExtensionVersion
38 | }
39 | }
40 |
41 | dependencies {
42 |
43 | implementation(libs.androidx.core.ktx)
44 | implementation(libs.androidx.lifecycle.runtime.ktx)
45 | implementation(libs.androidx.activity.compose)
46 | implementation(platform(libs.androidx.compose.bom))
47 | implementation(libs.androidx.compose.ui)
48 | implementation(libs.androidx.compose.ui.graphics)
49 | implementation(libs.androidx.compose.ui.preview)
50 | implementation(libs.androidx.compose.material3)
51 | implementation(libs.androidx.constraintlayout)
52 |
53 | testImplementation(libs.junit)
54 | androidTestImplementation(libs.androidx.junit)
55 | androidTestImplementation(libs.androidx.espresso.core)
56 | androidTestImplementation(platform(libs.androidx.compose.bom))
57 | androidTestImplementation(libs.androidx.compose.ui.test.junit4)
58 | debugImplementation(libs.androidx.compose.ui.tooling)
59 | debugImplementation(libs.androidx.compose.ui.test.manifest)
60 |
61 | implementation(project(":core:designsystem"))
62 | }
--------------------------------------------------------------------------------
/core/network/src/main/java/com/ahmetocak/network/datasource/firebase/firestore/FirebaseFirestoreDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.network.datasource.firebase.firestore
2 |
3 | import com.ahmetocak.network.model.firebase.firestore.NetworkWatchListMovie
4 | import com.ahmetocak.network.helpers.FirebaseConstants
5 | import com.google.android.gms.tasks.Task
6 | import com.google.firebase.auth.FirebaseAuth
7 | import com.google.firebase.firestore.DocumentSnapshot
8 | import com.google.firebase.firestore.FieldValue
9 | import com.google.firebase.firestore.FirebaseFirestore
10 | import com.google.firebase.firestore.SetOptions
11 | import javax.inject.Inject
12 |
13 | class FirebaseFirestoreDataSourceImpl @Inject constructor(
14 | private val firestoreDb: FirebaseFirestore,
15 | private val firebaseAuth: FirebaseAuth
16 | ) : FirebaseFirestoreDataSource {
17 |
18 | override fun addMovieData(watchListMovie: NetworkWatchListMovie): Task {
19 | return firestoreDb.collection(FirebaseConstants.Firestore.WATCH_LIST_COLLECTION_NAME)
20 | .document(firebaseAuth.currentUser?.uid ?: "")
21 | .set(
22 | hashMapOf(FirebaseConstants.Firestore.WATCH_LIST_ARRAY_NAME to FieldValue.arrayUnion(watchListMovie)),
23 | SetOptions.merge()
24 | )
25 | }
26 |
27 | override fun updateMovieData(watchListMovie: List): Task {
28 | return firestoreDb.collection(FirebaseConstants.Firestore.WATCH_LIST_COLLECTION_NAME)
29 | .document(firebaseAuth.currentUser?.uid ?: "")
30 | .update(mapOf(FirebaseConstants.Firestore.WATCH_LIST_ARRAY_NAME to watchListMovie))
31 | }
32 |
33 | override fun getMovieData(): Task {
34 | return firestoreDb.collection(FirebaseConstants.Firestore.WATCH_LIST_COLLECTION_NAME)
35 | .document(firebaseAuth.currentUser?.uid ?: "")
36 | .get()
37 | }
38 |
39 | override fun deleteMovieDocument(): Task {
40 | return firestoreDb.collection(FirebaseConstants.Firestore.WATCH_LIST_COLLECTION_NAME)
41 | .document(firebaseAuth.currentUser?.uid ?: "")
42 | .delete()
43 | }
44 | }
--------------------------------------------------------------------------------
/core/domain/src/main/java/com/ahmetocak/domain/firebase/auth/ReAuthenticateUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ahmetocak.domain.firebase.auth
2 |
3 | import com.ahmetocak.authentication.firebase.FirebaseAuthClient
4 | import com.ahmetocak.authentication.firebase.GoogleAuthClient
5 | import com.ahmetocak.common.helpers.UiText
6 | import com.ahmetocak.common.helpers.handleTaskError
7 | import com.google.firebase.auth.FirebaseAuth
8 | import com.google.firebase.auth.GoogleAuthProvider
9 | import javax.inject.Inject
10 |
11 | class ReAuthenticateUseCase @Inject constructor(
12 | private val firebaseAuthClient: FirebaseAuthClient,
13 | private val googleAuthClient: GoogleAuthClient
14 | ) {
15 | operator fun invoke(email: String, password: String, onTaskSuccess: () -> Unit, onTaskFailed: (UiText) -> Unit) {
16 | val signInProvider = FirebaseAuth.getInstance().getAccessToken(false).result.signInProvider
17 |
18 | if (signInProvider == GoogleAuthProvider.PROVIDER_ID) {
19 | googleAuthClient.reAuthenticate().addOnCompleteListener { task ->
20 | if (task.isSuccessful) {
21 | val account = task.result
22 | val credential = GoogleAuthProvider.getCredential(account.idToken, null)
23 |
24 | FirebaseAuth.getInstance().currentUser?.reauthenticate(credential)
25 | ?.addOnCompleteListener { reAuthTask ->
26 | if (reAuthTask.isSuccessful) {
27 | onTaskSuccess()
28 | } else {
29 | onTaskFailed(handleTaskError(reAuthTask.exception))
30 | }
31 | }
32 | } else {
33 | onTaskFailed(handleTaskError(task.exception))
34 | }
35 | }
36 | } else {
37 | firebaseAuthClient.reAuthenticate(email, password)?.addOnCompleteListener { task ->
38 | if (task.isSuccessful) {
39 | onTaskSuccess()
40 | } else {
41 | onTaskFailed(handleTaskError(task.exception))
42 | }
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------