diff --git a/Sources/app/build.gradle b/Sources/app/build.gradle index 3440a82..d8696c7 100644 --- a/Sources/app/build.gradle +++ b/Sources/app/build.gradle @@ -36,31 +36,48 @@ android { kotlinOptions { jvmTarget = '1.8' } + buildFeatures { + dataBinding true + } } dependencies { + implementation 'androidx.core:core-ktx:1.9.0' implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion" + implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.activity:activity-ktx:$rootProject.activityVersion" + implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion" + implementation "com.google.android.material:material:$rootProject.materialVersion" // Room components + implementation "androidx.room:room-runtime:$rootProject.roomVersion" implementation "androidx.room:room-ktx:$rootProject.roomVersion" kapt "androidx.room:room-compiler:$rootProject.roomVersion" - androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion" // Lifecycle components implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion" - implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$rootProject.lifecycleVersion" // Kotlin components implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines" api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines" + //Coil + implementation "io.coil-kt:coil:1.1.1" + // UI - implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion" - implementation "com.google.android.material:material:$rootProject.materialVersion" + + + // Moshi + implementation "com.squareup.moshi:moshi-kotlin:1.13.0" + + // Retrofit & Moshi + implementation "com.squareup.retrofit2:retrofit:2.9.0" + implementation "com.squareup.retrofit2:converter-scalars:2.9.0" + implementation "com.squareup.retrofit2:converter-moshi:2.9.0" // Testing testImplementation "junit:junit:$rootProject.junitVersion" diff --git a/Sources/app/src/main/AndroidManifest.xml b/Sources/app/src/main/AndroidManifest.xml index a4559cd..6d1f5dd 100644 --- a/Sources/app/src/main/AndroidManifest.xml +++ b/Sources/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ + + + tools:targetApi="33"> diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/MovieApplication.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/MovieApplication.kt new file mode 100644 index 0000000..9e750db --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/MovieApplication.kt @@ -0,0 +1,12 @@ +package fr.iut.pm.movieapplication + +import android.app.Application +import androidx.room.Room +import fr.iut.pm.movieapplication.data.database.MovieDataBase + +class MovieApplication : Application() { + override fun onCreate() { + super.onCreate() + MovieDataBase.initialize(this) + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/MovieApplicationAPI.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/MovieApplicationAPI.kt new file mode 100644 index 0000000..438381e --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/MovieApplicationAPI.kt @@ -0,0 +1,57 @@ +package fr.iut.pm.movieapplication.api + +import fr.iut.pm.movieapplication.api.dtos.MovieDetailsDTO +import fr.iut.pm.movieapplication.api.dtos.ResultDTO +import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface MovieApplicationAPI { + + // Movie + @GET("movie/popular") + suspend fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + + @GET("movie/now_playing") + suspend fun getNowPlayingMovies(@Query("api_key") apiKey : String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + + @GET("movie/upcoming") + suspend fun getUpcomingMovies(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + + @GET("movie/top_rated") + suspend fun getTopRatedMovies(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + + // Movie details + @GET("movie/{movie_id}") + suspend fun getMovieDetails(@Path("movie_id") movieId : Int, @Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr") : Response + + + // TvShow + @GET("tv/popular") + suspend fun getPopularTvShows(@Query("api_key") apiKey : String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + @GET("tv/airing_today") + suspend fun getAiringTodayTvShows(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + @GET("tv/on_the_air") + suspend fun getTvOnTheAirTvShows(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + @GET("tv/top_rated") + suspend fun getTopRatedTvShows(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response + + + @GET("trending/{media_type}/{time_window}") + suspend fun getTrending(@Path("media_type") mediaType : String = "all", @Path("time_window") timeWindow : String = "day", @Query("api_key") apiKey: String = API_KEY ) : Response + + @GET("search/multi") + suspend fun searchMedia( + @Query("query") query: String, + @Query("page") page: Int = 1, + @Query("api_key") apiKey: String = API_KEY, + @Query("language") language: String = "fr", + @Query("include_adult") includeAdult: Boolean = true + ): Response + + @GET("tv/{tv_id}") + fun getShowDetails(@Path("tv_id") tvId : Int, @Query("api_key") apiKey: String = API_KEY) + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/RetrofitInstance.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/RetrofitInstance.kt new file mode 100644 index 0000000..1ade6b0 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/RetrofitInstance.kt @@ -0,0 +1,23 @@ +package fr.iut.pm.movieapplication.api + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import fr.iut.pm.movieapplication.utils.Constants.Companion.BASE_URL +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + + + +object RetrofitInstance { + + private val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + + private val retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + + val api: MovieApplicationAPI = retrofit.create(MovieApplicationAPI::class.java) +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/config/GlobalImageConfig.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/config/GlobalImageConfig.kt new file mode 100644 index 0000000..74063eb --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/config/GlobalImageConfig.kt @@ -0,0 +1,51 @@ +package fr.iut.pm.movieapplication.api.config + +import android.util.Log +import com.squareup.moshi.Json + + +object GlobalImageConfig { + @Json(name = "base_url") + private var _baseUrl : String = "" + val baseUrl : String = _baseUrl + @Json(name = "secure_base_url") + private var _secureBaseUrl : String = "" + val secureBaseUrl = _secureBaseUrl + @Json(name = "backdrop_sizes") + private var backdropSizes : List = listOf() + @Json(name = "logo_sizes") + private var logoSizes : List = listOf() + @Json(name = "poster_sizes") + private var posterSizes : List = listOf() + @Json(name = "profile_sizes") + private var profilSizes : List = listOf() + @Json(name = "still_sizes") + private var stillSizes : List = listOf() + + fun updateConfig(config: ImageConfig) { + Log.d("BASE URL IMAGE", baseUrl) + _baseUrl = config.baseUrl + _secureBaseUrl = config.secureBaseUrl + posterSizes = config.posterSizes + backdropSizes = config.backdropSizes + } + +} + +data class ImageConfig( + + @Json(name = "images.base_url") + val baseUrl : String, + @Json(name = "images.secure_base_url") + val secureBaseUrl : String, + @Json(name = "images.backdrop_sizes") + val backdropSizes : List, + @Json(name = "images.logo_sizes") + val logoSizes : List, + @Json(name = "images.poster_sizes") + val posterSizes : List, + @Json(name = "images.profile_sizes") + val profileSizes : List, + @Json(name = "images.still_sizes") + val stillSizes : List +) \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/GenreDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/GenreDTO.kt new file mode 100644 index 0000000..8bbdaa4 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/GenreDTO.kt @@ -0,0 +1,8 @@ +package fr.iut.pm.movieapplication.api.dtos + +data class GenreDTO( + val id : Int, + val name : String +) { + +} diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MediaResultDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MediaResultDTO.kt new file mode 100644 index 0000000..13e9c02 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MediaResultDTO.kt @@ -0,0 +1,42 @@ +package fr.iut.pm.movieapplication.api.dtos + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@Json(name = "results") +open class MediaResultDTO( + + @Json(name = "poster_path") + val posterPath : String?, + val adult : Boolean?, + val overview : String? = null, + @Json(name = "first_air_date") + val firstAirDate : String? = null, + @Json(name = "release_date") + val releaseDate : String? = null, + @Json(name = "origin_country") + val originCountry : List? = null, +// @Json(name = "genre_ids") +// val genreIds : List, + val id : Int, + @Json(name = "original_title") + val originalTitle : String? = null, + @Json(name = "original_language") + val originalLanguage : String? = null, + val title : String? = null, + @Json(name = "backdrop_path") + val backdropPath : String?, + val popularity : Double?, + @Json(name = "vote_count") + val voteCount : Int?, + //val video : Boolean?, + @Json(name = "vote_average") + val voteAverage : Double?, + val name: String? = null, + @Json(name = "original_name") + val originalName : String? = null, + @Json(name = "media_type") + val mediaType : String? = null +) { + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDTO.kt new file mode 100644 index 0000000..d7cf509 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDTO.kt @@ -0,0 +1,29 @@ +package fr.iut.pm.movieapplication.api.dtos + +import com.squareup.moshi.Json + +open class MovieDTO ( + @Json(name = "poster_path") + open val posterPath: String?, + open val adult: Boolean, + open val overview: String?, + @Json(name = "release_date") + open val releaseDate: String, + open val id: Int, + @Json(name = "original_title") + open val originalTitle: String, + @Json(name = "original_language") + open val originalLanguage: String, + open val title: String, + @Json(name = "backdrop_path") + open val backdropPath: String?, + open val popularity: Double, + @Json(name = "vote_count") + open val voteCount: Int, + @Json(name = "vote_average") + open val voteAverage: Double + + +){ + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDetailsDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDetailsDTO.kt new file mode 100644 index 0000000..248dbab --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieDetailsDTO.kt @@ -0,0 +1,37 @@ +package fr.iut.pm.movieapplication.api.dtos + +import com.squareup.moshi.Json +import fr.iut.pm.movieapplication.model.Genre + +data class MovieDetailsDTO( + + val adult: Boolean, + @Json(name = "backdrop_path") + val backdropPath: String?, + val budget: Int, + val genres: List, + val homepage: String?, + val id: Int, + @Json(name = "original_language") + val originalLanguage: String, + @Json(name = "original_title") + val originalTitle: String, + val overview: String?, + val popularity: Double, + @Json(name = "poster_path") + val posterPath: String?, + //prod companies + //prod countries + @Json(name = "release_date") + val releaseDate: String, + val revenue: Long, + //spoken language + val status: String, + val title: String, + @Json(name = "vote_average") + val voteAverage: Double, + @Json(name = "vote_count") + val voteCount: Int + +) +{} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieResultDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieResultDTO.kt new file mode 100644 index 0000000..bc1c340 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/MovieResultDTO.kt @@ -0,0 +1,39 @@ +package fr.iut.pm.movieapplication.api.dtos + + +import com.squareup.moshi.Json + +class MovieResultDTO( + @Json(name = "poster_path") + val posterPath : String?, + @Json(name = "adult") + val adult : Boolean, + @Json(name = "overview") + val overview : String, + @Json(name = "release_date") + val releaseDate : String?, + @Json(name = "genre_ids") + val genreIds : List?, + @Json(name = "id") + val id : Int, + @Json(name = "original_title") + val originalTitle : String?, + @Json(name = "original_language") + val originalLanguage : String?, + @Json(name = "title") + val title : String?, + @Json(name = "backdrop_path") + val backdropPath : String?, + @Json(name = "popularity") + val popularity : Double?, + @Json(name = "vote_count") + val voteCount : Int?, + @Json(name = "vote_average") + val voteAverage : Double? + + + +) { + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/ResultDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/ResultDTO.kt new file mode 100644 index 0000000..f52a776 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/ResultDTO.kt @@ -0,0 +1,18 @@ +package fr.iut.pm.movieapplication.api.dtos + + +import com.squareup.moshi.Json + + + +class ResultDTO( + @Json(name = "page") + val page : Int, + @Json(name = "results") + val results : List, + @Json(name = "total_results") + val totalResults : Int, + @Json(name = "total_pages") + val totalPages : Int + +) {} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/TvShowDetailsDTO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/TvShowDetailsDTO.kt new file mode 100644 index 0000000..bdee72e --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/api/dtos/TvShowDetailsDTO.kt @@ -0,0 +1,30 @@ +package fr.iut.pm.movieapplication.api.dtos + +import com.squareup.moshi.Json + +open class TvShowDetailsDTO( + + @Json(name = "poster_path") + open val posterPath: String?, + open val popularity: Double, + open val id: Int, + @Json(name = "backdrop_path") + open val backdropPath: String?, + @Json(name = "vote_average") + open val voteAverage: Double, + open val overview: String, + @Json(name = "first_air_date") + open val firstAirDate: String, + @Json(name = "origin_country") + open val originCountry: List, + @Json(name = "genre_ids") + open val genreIds: List, + @Json(name = "original_language") + open val originalLanguage: String, + @Json(name = "vote_count") + open val voteCount: Int, + open val name: String, + @Json(name = "original_name") + open val originalName: String, + ) +{} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/dao/MovieDAO.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/dao/MovieDAO.kt index bd50cd9..af5ae2d 100644 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/dao/MovieDAO.kt +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/dao/MovieDAO.kt @@ -1,15 +1,61 @@ package fr.iut.pm.movieapplication.data.dao -import androidx.room.Dao -import androidx.room.Query -import fr.iut.pm.movieapplication.model.Movie +import androidx.room.* +import fr.iut.pm.movieapplication.data.entities.* +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails import kotlinx.coroutines.flow.Flow @Dao interface MovieDAO { - @Query("SELECT * FROM movies_table ORDER BY original_title ASC") - fun getMovieByAlphabetizeMovie() : Flow> + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(movieEntity : MovieEntity) + + /** + * Get all movies from the movies_table + */ + @Query("SELECT * FROM movies_table") + suspend fun getAllMovies() : List + + /** + * Get all movies from the movies_details_table + */ + @Query("SELECT * FROM movies_details_table") + suspend fun getAllMoviesDetails() : List + + /** + * Get a movie by id + */ + @Query("SELECT * FROM movies_table WHERE id = :id") + fun getMovieById(id : Int) : MovieEntity + + //delete a movie + @Query("DELETE FROM movies_table WHERE id = :id") + suspend fun deleteMovieById(id : Int) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertMovieDetails(movieDetailsEntity: MovieDetailsEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertGenres(genres : List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertMovieDetailsGenres(movieDetailsGenresEntity : List) + + @Transaction + suspend fun insertMovieDetailsWithGenres(movieDetailsWithGenres : MovieDetailsWithGenres) { + insert(movieDetailsWithGenres.movieDetails.movie) + insertMovieDetails(movieDetailsWithGenres.movieDetails) + insertGenres(movieDetailsWithGenres.genres) + val movieDetailsGenres = movieDetailsWithGenres.genres.map { + MovieDetailsGenreEntity(movieDetailsWithGenres.movieDetails.movieId, it.genreId) + } + insertMovieDetailsGenres(movieDetailsGenres) + } + + @Transaction + @Query("SELECT * FROM movies_details_table WHERE movie_id = :movieId") + suspend fun getMovieDetailsWithGenresById(movieId: Int): MovieDetailsWithGenres } \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/database/MovieDataBase.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/database/MovieDataBase.kt new file mode 100644 index 0000000..5e8aaa2 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/database/MovieDataBase.kt @@ -0,0 +1,60 @@ +package fr.iut.pm.movieapplication.data.database + +import android.app.Application +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import fr.iut.pm.movieapplication.MovieApplication +import fr.iut.pm.movieapplication.data.dao.MovieDAO +import fr.iut.pm.movieapplication.data.entities.GenreEntity +import fr.iut.pm.movieapplication.data.entities.MovieDetailsEntity +import fr.iut.pm.movieapplication.data.entities.MovieDetailsGenreEntity +import fr.iut.pm.movieapplication.data.entities.MovieEntity + + +const val MOVIE_DB_NAME = "movies.db" + +@Database( entities = [MovieEntity::class, MovieDetailsEntity::class, GenreEntity::class, MovieDetailsGenreEntity::class], version = 3) +abstract class MovieDataBase : RoomDatabase() +{ + abstract fun movieDAO() : MovieDAO + + companion object + { + private lateinit var application : Application + + @Volatile + private var instance : MovieDataBase? = null + + fun getInstance() : MovieDataBase + { + if (::application.isInitialized) { + if (instance == null) + synchronized(this) + { + if (instance == null) + instance = Room.databaseBuilder( + application.applicationContext, + MovieDataBase::class.java, + MOVIE_DB_NAME + ) + .fallbackToDestructiveMigration() + .build() + } + return instance!! + } + else + throw RuntimeException("the database must be first initialized") + } + + @Synchronized + fun initialize(app : MovieApplication) + { + if (::application.isInitialized) + throw RuntimeException("the database must not be initialized twice") + + application = app + } + } +} + diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/GenreEntity.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/GenreEntity.kt new file mode 100644 index 0000000..e6b9788 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/GenreEntity.kt @@ -0,0 +1,12 @@ +package fr.iut.pm.movieapplication.data.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "genres_table") +data class GenreEntity( + @PrimaryKey @ColumnInfo("genre_id") val genreId : Int, + val name : String +) { +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsEntity.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsEntity.kt new file mode 100644 index 0000000..269c273 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsEntity.kt @@ -0,0 +1,19 @@ +package fr.iut.pm.movieapplication.data.entities + +import androidx.room.* +import fr.iut.pm.movieapplication.model.Genre + + +@Entity(tableName = "movies_details_table") +data class MovieDetailsEntity( + @PrimaryKey @ColumnInfo("movie_id") val movieId : Int, + @Embedded val movie : MovieEntity, + val budget : Int, + val homepage : String?, + val revenue : Long, + val status : String + //Future attributes to add +) { + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsGenreEntity.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsGenreEntity.kt new file mode 100644 index 0000000..538f94c --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsGenreEntity.kt @@ -0,0 +1,17 @@ +package fr.iut.pm.movieapplication.data.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey + +@Entity(tableName = "movie_details_genre", + primaryKeys = ["movie_id", "genre_id"], + foreignKeys = [ + ForeignKey(entity = MovieDetailsEntity::class, parentColumns = ["movie_id"], childColumns = ["movie_id"]), + ForeignKey(entity = GenreEntity::class, parentColumns = ["genre_id"], childColumns = ["genre_id"]) + ] +) +data class MovieDetailsGenreEntity( + @ColumnInfo("movie_id") val movieId : Int, + @ColumnInfo("genre_id") val genreId : Int +) \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsWithGenres.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsWithGenres.kt new file mode 100644 index 0000000..cf15e8e --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieDetailsWithGenres.kt @@ -0,0 +1,15 @@ +package fr.iut.pm.movieapplication.data.entities + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation + +data class MovieDetailsWithGenres( + @Embedded val movieDetails: MovieDetailsEntity, + @Relation( + parentColumn = "movie_id", + entityColumn = "genre_id", + associateBy = Junction(MovieDetailsGenreEntity::class) + ) + val genres: List +) \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieEntity.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieEntity.kt new file mode 100644 index 0000000..217bd5a --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/entities/MovieEntity.kt @@ -0,0 +1,23 @@ +package fr.iut.pm.movieapplication.data.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "movies_table") +data class MovieEntity( + @PrimaryKey val id: Int, + val title: String, + val overview: String?, + @ColumnInfo("poster_path") val posterPath: String?, + @ColumnInfo("backdrop_path") val backdropPath: String?, + @ColumnInfo("release_date") val releaseDate: String, + @ColumnInfo("vote_average") val voteAverage: Double, + @ColumnInfo("vote_count") val voteCount: Int, + val popularity: Double, + @ColumnInfo("original_language") val originalLanguage: String, + @ColumnInfo("original_title") val originalTitle: String, + val adult: Boolean, + @ColumnInfo("is_favorite") val isFavorite: Boolean +) { +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/GenreLocalMapper.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/GenreLocalMapper.kt new file mode 100644 index 0000000..a444333 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/GenreLocalMapper.kt @@ -0,0 +1,34 @@ +package fr.iut.pm.movieapplication.data.mapper + +import fr.iut.pm.movieapplication.data.entities.GenreEntity +import fr.iut.pm.movieapplication.model.Genre + +object GenreLocalMapper { + + /** + * Map a [Genre] to a [GenreEntity] + */ + fun mapToGenreEntity(genre : Genre) : GenreEntity { + return GenreEntity( + genreId = genre.id, + name = genre.name + ) + } + + /** + * Map a list of [Genre] to a list of [GenreEntity] + */ + fun mapToGenreEntities(genres : List) : List { + return genres.map { mapToGenreEntity(it) } + } + + /** + * Map a [GenreEntity] to a [Genre] + */ + fun mapToGenre(genreEntity : GenreEntity) : Genre { + return Genre( + id = genreEntity.genreId, + name = genreEntity.name + ) + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/MovieLocalMapper.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/MovieLocalMapper.kt new file mode 100644 index 0000000..fef1f61 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/data/mapper/MovieLocalMapper.kt @@ -0,0 +1,101 @@ +package fr.iut.pm.movieapplication.data.mapper + +import fr.iut.pm.movieapplication.data.entities.MovieDetailsEntity +import fr.iut.pm.movieapplication.data.entities.MovieDetailsWithGenres +import fr.iut.pm.movieapplication.data.entities.MovieEntity +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails + +object MovieLocalMapper { + + /** + * Map a [MovieEntity] to a [Movie] + */ + fun mapToMovie(movieEntity : MovieEntity) : Movie { + return Movie( + posterPath = movieEntity.posterPath, + adult = movieEntity.adult, + overview = movieEntity.overview, + releaseDate = movieEntity.releaseDate, + id = movieEntity.id, + originalTitle = movieEntity.originalTitle, + originalLanguage = movieEntity.originalLanguage, + title = movieEntity.title, + backdropPath = movieEntity.backdropPath, + popularity = movieEntity.popularity, + voteCount = movieEntity.voteCount, + voteAverage = movieEntity.voteAverage + ) + } + + + /** + * Map a [Movie] to a [MovieEntity] + */ + fun mapToMovieEntity(movie : Movie) : MovieEntity { + return MovieEntity( + posterPath = movie.posterPath, + adult = movie.adult!!, + overview = movie.overview, + releaseDate = movie.releaseDate!!, + id = movie.id, + originalTitle = movie.originalTitle, + originalLanguage = movie.originalLanguage, + title = movie.title, + backdropPath = movie.backdropPath , + popularity = movie.popularity, + voteCount = movie.voteCount, + voteAverage = movie.voteAverage, + isFavorite = false + ) + } + + /** + * Map a [MovieDetails] to a [MovieDetailsEntity] + */ + fun mapToMovieDetailsEntity(movieDetails : MovieDetails) : MovieDetailsEntity { + return MovieDetailsEntity( + movieId = movieDetails.id, + movie = mapToMovieEntity(movieDetails), + budget = movieDetails.budget, + homepage = movieDetails.homepage, + revenue = movieDetails.revenue, + status = movieDetails.status + ) + } + + /** + * Map a [MovieDetails] to a [MovieDetailsWithGenres] + */ + fun mapToMovieDetailsWithGenres(movieDetails: MovieDetails) : MovieDetailsWithGenres { + return MovieDetailsWithGenres( + movieDetails = mapToMovieDetailsEntity(movieDetails), + genres = movieDetails.genres.map { GenreLocalMapper.mapToGenreEntity(it) } + ) + } + + /** + * Map a [MovieDetailsWithGenres] to a [MovieDetails] + */ + fun mapToMovieDetails(movieDetailsWithGenres: MovieDetailsWithGenres) : MovieDetails { + return MovieDetails( + id = movieDetailsWithGenres.movieDetails.movieId, + posterPath = movieDetailsWithGenres.movieDetails.movie.posterPath, + adult = movieDetailsWithGenres.movieDetails.movie.adult, + overview = movieDetailsWithGenres.movieDetails.movie.overview, + releaseDate = movieDetailsWithGenres.movieDetails.movie.releaseDate, + originalTitle = movieDetailsWithGenres.movieDetails.movie.originalTitle, + originalLanguage = movieDetailsWithGenres.movieDetails.movie.originalLanguage, + title = movieDetailsWithGenres.movieDetails.movie.title, + backdropPath = movieDetailsWithGenres.movieDetails.movie.backdropPath, + popularity = movieDetailsWithGenres.movieDetails.movie.popularity, + voteCount = movieDetailsWithGenres.movieDetails.movie.voteCount, + voteAverage = movieDetailsWithGenres.movieDetails.movie.voteAverage, + budget = movieDetailsWithGenres.movieDetails.budget, + homepage = movieDetailsWithGenres.movieDetails.homepage, + revenue = movieDetailsWithGenres.movieDetails.revenue, + status = movieDetailsWithGenres.movieDetails.status, + genres = movieDetailsWithGenres.genres.map { GenreLocalMapper.mapToGenre(it) } + ) + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Genre.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Genre.kt index 62c322d..f0a8111 100644 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Genre.kt +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Genre.kt @@ -1,16 +1,7 @@ package fr.iut.pm.movieapplication.model - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "genre_table") data class Genre( - @PrimaryKey - @ColumnInfo(name = "id") - var id : Int, - @ColumnInfo(name = "name") - var name : String + val id : Int, + val name : String ) { override fun equals(other: Any?): Boolean { diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Movie.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Movie.kt deleted file mode 100644 index 6269f00..0000000 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Movie.kt +++ /dev/null @@ -1,71 +0,0 @@ -package fr.iut.pm.movieapplication.model - -import androidx.room.ColumnInfo -import androidx.room.Embedded -import androidx.room.Entity -import androidx.room.PrimaryKey -import androidx.room.Relation -import java.util.Date - -@Entity(tableName = "movies_table") -data class Movie( - @ColumnInfo(name = "adult") - val adult: Boolean, - @ColumnInfo(name = "budget") - val budget: Int, - @ColumnInfo(name = "genres") - val genres: Array, - @ColumnInfo(name = "homepage") - val homePage: String?, - @PrimaryKey - @ColumnInfo(name = "id") - val movieId: Int, - @ColumnInfo(name = "original_language") - val originalLanguage: String, - @ColumnInfo(name = "original_title") - val originalTitle: String, - val overview: String?, - val popularity: Int, - @ColumnInfo(name = "poster_path") - val posterPath : String?, - @Embedded - val productionCompanies: Array, - @Relation( - parentColumn = "movieId", - entityColumn = "" - ) - @ColumnInfo(name = "production_countries") - val productionCountries: Array, - @ColumnInfo(name = "release_date") - val releaseDate: Date, - val revenue: Int, - val runtime: Int?, - //var spokenLanguages : Array, - val status: String, - @ColumnInfo(name = "tag_line") - val tagLine: String?, - val title: String, - @ColumnInfo(name = "vote_average") - val voteAverage: Number, - @ColumnInfo(name = "vote_count") - val voteCount : Int - - - ) { - - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Movie - - if (movieId != other.movieId) return false - - return true - } - - override fun hashCode(): Int { - return movieId - } -} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Popular.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Popular.kt new file mode 100644 index 0000000..c278e4d --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/Popular.kt @@ -0,0 +1,18 @@ +package fr.iut.pm.movieapplication.model + +import androidx.room.ColumnInfo +import androidx.room.Embedded +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails + +data class Popular( + @ColumnInfo("page") + val page : Int, + @ColumnInfo("results") + @Embedded + val results : List, + val totalResults : Int, + val totalPages : Int + +){ + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/MediaResult.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/MediaResult.kt new file mode 100644 index 0000000..ef3a9a2 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/MediaResult.kt @@ -0,0 +1,21 @@ +package fr.iut.pm.movieapplication.model.media + +data class MediaResult( + + val posterPath: String? = null, + val adult: Boolean?, + val overview: String, + val releaseDate: String, + val originCountry: List? = null, +// val genreIds: List, + val id: Int, + val originalTitle: String, + val originalLanguage: String, + val title: String, + val backdropPath: String? = null, + val popularity: Double, + val voteCount: Int, + val voteAverage: Double, + val mediaType: String? +) +{} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/Movie.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/Movie.kt new file mode 100644 index 0000000..c03daae --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/Movie.kt @@ -0,0 +1,55 @@ +package fr.iut.pm.movieapplication.model.media.movie + +open class Movie( + open val posterPath: String?, + open val adult: Boolean?, + open val overview: String?, + open val releaseDate: String?, + open val id: Int, + open val originalTitle: String, + open val originalLanguage: String, + open val title: String, + open val backdropPath: String?, + open val popularity: Double, + open val voteCount: Int, + open val voteAverage: Double +) +{ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Movie + + if (posterPath != other.posterPath) return false + if (adult != other.adult) return false + if (overview != other.overview) return false + if (releaseDate != other.releaseDate) return false + if (id != other.id) return false + if (originalTitle != other.originalTitle) return false + if (originalLanguage != other.originalLanguage) return false + if (title != other.title) return false + if (backdropPath != other.backdropPath) return false + if (popularity != other.popularity) return false + if (voteCount != other.voteCount) return false + if (voteAverage != other.voteAverage) return false + + return true + } + + override fun hashCode(): Int { + var result = posterPath?.hashCode() ?: 0 + result = 31 * result + adult.hashCode() + result = 31 * result + overview.hashCode() + result = 31 * result + releaseDate.hashCode() + result = 31 * result + id + result = 31 * result + originalTitle.hashCode() + result = 31 * result + originalLanguage.hashCode() + result = 31 * result + (title?.hashCode() ?: 0) + result = 31 * result + (backdropPath?.hashCode() ?: 0) + result = 31 * result + popularity.hashCode() + result = 31 * result + voteCount + result = 31 * result + voteAverage.hashCode() + return result + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/MovieDetails.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/MovieDetails.kt new file mode 100644 index 0000000..7bb297f --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/movie/MovieDetails.kt @@ -0,0 +1,34 @@ +package fr.iut.pm.movieapplication.model.media.movie + +import fr.iut.pm.movieapplication.model.Genre + + +class MovieDetails( + + adult : Boolean?, + backdropPath : String?, + val budget : Int, + val genres : List, + val homepage : String?, + id : Int, + originalLanguage : String, + originalTitle : String, + overview : String?, + popularity : Double, + posterPath : String?, + //prod companies + //prod countries + releaseDate : String?, + val revenue : Long, + //spoken language + val status : String, + title : String, + voteAverage : Double, + voteCount : Int + +) : Movie(posterPath, adult, overview, releaseDate, id, originalTitle, originalLanguage, + title, backdropPath, popularity, voteCount, voteAverage) +{ + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShow.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShow.kt new file mode 100644 index 0000000..22a8bfd --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShow.kt @@ -0,0 +1,62 @@ +package fr.iut.pm.movieapplication.model.media.tvshow + +open class TvShow( + open val posterPath: String?, + open val popularity: Double, + open val id: Int, + open val backdropPath: String, + open val voteAverage: Double, + open val overview: String, + open val firstAirDate: String?, + open val originCountry: List, +// open val genreIds: List, + open val originalLanguage: String, + open val voteCount: Int, + open val name: String, + open val originalName: String, + open val mediaType: String = "tv", +) +{ + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TvShow + + if (posterPath != other.posterPath) return false + if (popularity != other.popularity) return false + if (id != other.id) return false + if (backdropPath != other.backdropPath) return false + if (voteAverage != other.voteAverage) return false + if (overview != other.overview) return false + if (firstAirDate != other.firstAirDate) return false + if (originCountry != other.originCountry) return false +// if (genreIds != other.genreIds) return false + if (originalLanguage != other.originalLanguage) return false + if (voteCount != other.voteCount) return false + if (name != other.name) return false + if (originalName != other.originalName) return false + if (mediaType != other.mediaType) return false + + return true + } + + override fun hashCode(): Int { + var result = posterPath?.hashCode() ?: 0 + result = 31 * result + popularity.hashCode() + result = 31 * result + id + result = 31 * result + backdropPath.hashCode() + result = 31 * result + voteAverage.hashCode() + result = 31 * result + overview.hashCode() + result = 31 * result + (firstAirDate?.hashCode() ?: 0) + result = 31 * result + originCountry.hashCode() +// result = 31 * result + genreIds.hashCode() + result = 31 * result + originalLanguage.hashCode() + result = 31 * result + voteCount + result = 31 * result + name.hashCode() + result = 31 * result + originalName.hashCode() + result = 31 * result + mediaType.hashCode() + return result + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShowDetails.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShowDetails.kt new file mode 100644 index 0000000..7a96534 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/model/media/tvshow/TvShowDetails.kt @@ -0,0 +1,4 @@ +package fr.iut.pm.movieapplication.model.media.tvshow + +class TvShowDetails { +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MediaRepository.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MediaRepository.kt new file mode 100644 index 0000000..e8b771b --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MediaRepository.kt @@ -0,0 +1,92 @@ +package fr.iut.pm.movieapplication.repository.api + +import android.util.Log +import fr.iut.pm.movieapplication.api.RetrofitInstance +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.utils.MediaResultMapper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class MediaRepository { + + suspend fun getTrends() : List = withContext(Dispatchers.IO) { + val listMediaResult : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getTrending() + if(response.isSuccessful) { + + val listMediaResultDTO = response.body()?.results + Log.d("Response",response.body().toString()) + listMediaResultDTO?.forEach { + + val mediaResult = MediaResultMapper.mapToMediaResult(it) + listMediaResult.add(mediaResult) + mediaResult.title?.let { it1 -> Log.d("Movie", it1) } + + } + } + else Log.d("ERROR FAILED", response.message()) + listMediaResult + } + + suspend fun getPopularMovies() : List = withContext(Dispatchers.IO) { + val listMediaResult : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getPopularMovies() + if(response.isSuccessful) { + + val listMediaResultDTO = response.body()?.results + Log.d("Response",response.body().toString()) + listMediaResultDTO?.forEach { + + val mediaResult = MediaResultMapper.mapToMediaResult(it) + listMediaResult.add(mediaResult) + mediaResult.title?.let { it1 -> Log.d("Movie", it1) } + + } + } + else Log.d("ERROR FAILED", response.message()) + listMediaResult + } + + suspend fun getPopularTvShows(): List = withContext(Dispatchers.IO) { + val listMediaResult : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getPopularTvShows() + if(response.isSuccessful) { + + val listMediaResultDTO = response.body()?.results + Log.d("Response",response.body().toString()) + listMediaResultDTO?.forEach { + + val mediaResult = MediaResultMapper.mapToMediaResult(it) + listMediaResult.add(mediaResult) + mediaResult.title?.let { it1 -> Log.d("Movie", it1) } + + } + } + else Log.d("ERROR FAILED", response.message()) + listMediaResult + } + + suspend fun search(query : String, Page : Int = 1) : List = withContext(Dispatchers.IO) { + val listMediaResult : MutableList = mutableListOf() + + val response = RetrofitInstance.api.searchMedia(query, Page) + if(response.isSuccessful) { + + val listMediaResultDTO = response.body()?.results + Log.d("Response",response.body().toString()) + listMediaResultDTO?.forEach { + if(it?.mediaType == "movie" || it?.mediaType == "tv") { + val mediaResult = it?.let { it1 -> MediaResultMapper.mapToMediaResult(it1) } + mediaResult?.let { it1 -> listMediaResult.add(it1) } + mediaResult?.let { it1 -> Log.d("Movie", it1.title) } + } + + } + } + else Log.d("ERROR FAILED", response.message()) + listMediaResult + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MovieAPIRepository.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MovieAPIRepository.kt new file mode 100644 index 0000000..cde6f9b --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/MovieAPIRepository.kt @@ -0,0 +1,104 @@ +package fr.iut.pm.movieapplication.repository.api + +import android.util.Log +import fr.iut.pm.movieapplication.api.RetrofitInstance +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails +import fr.iut.pm.movieapplication.utils.MediaResultMapper +import fr.iut.pm.movieapplication.utils.MovieMapper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class MovieAPIRepository { + suspend fun getPopularMovies(page : Int = 1) : List = withContext(Dispatchers.IO) + { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getPopularMovies(page = page) + if(response.isSuccessful) { + val listMediaResultDTO = response.body()?.results + listMediaResultDTO?.forEach { + val movie = MediaResultMapper.mapToMovie(it) + listMovie.add(movie) + Log.d("Movie ", movie.title!! + " " + movie.id) + } + } + else Log.d("ERROR FAILED", response.message()) + listMovie + } + + suspend fun getNowPlayingMovies(page : Int = 1) : List = withContext(Dispatchers.IO) + { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getNowPlayingMovies(page = page) + if(response.isSuccessful) { + val listMediaResultDTO = response.body()?.results + listMediaResultDTO?.forEach { + val movie = MediaResultMapper.mapToMovie(it) + listMovie.add(movie) + Log.d("Movie ", movie.title!!) + } + } + else Log.d("ERROR FAILED", response.message()) + listMovie + } + + suspend fun getUpcomingMovies(page : Int = 1) : List = withContext(Dispatchers.IO) + { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getUpcomingMovies(page = page) + if(response.isSuccessful) { + val listMediaResultDTO = response.body()?.results + listMediaResultDTO?.forEach { + val movie = MediaResultMapper.mapToMovie(it) + listMovie.add(movie) + Log.d("Movie ", movie.title!!) + } + } + else Log.d("ERROR FAILED", response.message()) + listMovie + } + + suspend fun getTopRatedMovies(page : Int = 1) : List = withContext(Dispatchers.IO) + { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getTopRatedMovies(page = page) + if(response.isSuccessful) { + val listMediaResultDTO = response.body()?.results + listMediaResultDTO?.forEach { + val movie = MediaResultMapper.mapToMovie(it) + listMovie.add(movie) + Log.d("Movie ", movie.title!!) + } + } + else Log.d("ERROR FAILED", response.message()) + listMovie + } + + suspend fun getMovieDetails(id : Int) : MovieDetails? + { + var movieDetails : MovieDetails? = null + + val response = RetrofitInstance.api.getMovieDetails(id) + if(response.isSuccessful && response.body() != null) { + + Log.d("SUCCESS", response.body().toString()) + movieDetails = MovieMapper.mapToMovieDetails(response.body()!!) + Log.d("Movie details",movieDetails.toString()) + } + else Log.d("ERROR FAILED", response.toString()) + + + + return movieDetails + } + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/TvShowRepository.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/TvShowRepository.kt new file mode 100644 index 0000000..096de79 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/api/TvShowRepository.kt @@ -0,0 +1,89 @@ +package fr.iut.pm.movieapplication.repository.api + +import android.util.Log +import fr.iut.pm.movieapplication.api.RetrofitInstance +import fr.iut.pm.movieapplication.model.media.tvshow.TvShow +import fr.iut.pm.movieapplication.utils.MediaResultMapper + +class TvShowRepository { + + suspend fun getPopularTvShows(page : Int = 1 ) : List { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getPopularTvShows(page = page) + if (response.isSuccessful) { + Log.d("List :", response.body().toString()) + val popularDTO = response.body() + val listMoviesDTO = popularDTO?.results + listMoviesDTO?.forEach { + val tvShow = MediaResultMapper.mapToTvShow(it) + listMovie.add(tvShow) + Log.d("Movie ", tvShow.name ) + } + } + else Log.d("Error failure", response.message()) + + return listMovie + } + + suspend fun getAiringTodayTvShows(page : Int = 1 ) : List { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getAiringTodayTvShows(page = page) + if (response.isSuccessful) { + Log.d("List :", response.body().toString()) + val popularDTO = response.body() + val listMoviesDTO = popularDTO?.results + listMoviesDTO?.forEach { + val tvShow = MediaResultMapper.mapToTvShow(it) + listMovie.add(tvShow) + Log.d("Movie ", tvShow.name ) + } + } + else Log.d("Error failure", response.message()) + + return listMovie + } + + suspend fun getTvOnTheAirTvShows(page : Int = 1 ) : List { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getTvOnTheAirTvShows(page = page) + if (response.isSuccessful) { + Log.d("List :", response.body().toString()) + val popularDTO = response.body() + val listMoviesDTO = popularDTO?.results + listMoviesDTO?.forEach { + val tvShow = MediaResultMapper.mapToTvShow(it) + listMovie.add(tvShow) + Log.d("Movie ", tvShow.name ) + } + } + else Log.d("Error failure", response.message()) + + return listMovie + } + + suspend fun getTopRatedTvShows(page : Int = 1 ) : List { + + val listMovie : MutableList = mutableListOf() + + val response = RetrofitInstance.api.getTopRatedTvShows(page = page) + if (response.isSuccessful) { + Log.d("List :", response.body().toString()) + val popularDTO = response.body() + val listMoviesDTO = popularDTO?.results + listMoviesDTO?.forEach { + val tvShow = MediaResultMapper.mapToTvShow(it) + listMovie.add(tvShow) + Log.d("Movie ", tvShow.name ) + } + } + else Log.d("Error failure", response.message()) + + return listMovie + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/local/MovieLocalRepository.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/local/MovieLocalRepository.kt new file mode 100644 index 0000000..b06d2fe --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/repository/local/MovieLocalRepository.kt @@ -0,0 +1,36 @@ +package fr.iut.pm.movieapplication.repository.local + +import android.util.Log +import fr.iut.pm.movieapplication.data.dao.MovieDAO +import fr.iut.pm.movieapplication.data.mapper.MovieLocalMapper +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class MovieLocalRepository(private val movieDAO: MovieDAO) { + + suspend fun insertMovie(movie : Movie) = withContext(Dispatchers.IO) { + movieDAO.insert(MovieLocalMapper.mapToMovieEntity(movie)) + } + + suspend fun insertMovieDetails(movieDetails : MovieDetails) = withContext(Dispatchers.IO) { + Log.i("MovieLocalRepository", "INSERT MOVIE DETAILS") + movieDAO.insertMovieDetailsWithGenres(MovieLocalMapper.mapToMovieDetailsWithGenres(movieDetails)) + } + + suspend fun getMovieDetailsById(id : Int) : MovieDetails { + return MovieLocalMapper.mapToMovieDetails(movieDAO.getMovieDetailsWithGenresById(id)) + } + + suspend fun getAllMovies() : List = withContext(Dispatchers.IO) { + val listMovie : MutableList = mutableListOf() + val listMoviesEntities = movieDAO.getAllMovies() + + listMoviesEntities.forEach { movieEntity -> + listMovie.add(MovieLocalMapper.mapToMovie(movieEntity)) + } + + listMovie + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/activity/MainActivity.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/activity/MainActivity.kt index 1410f0f..cbe53d2 100644 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/activity/MainActivity.kt +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/activity/MainActivity.kt @@ -1,57 +1,74 @@ package fr.iut.pm.movieapplication.ui.activity -import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.view.View +import android.view.Menu +import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import com.google.android.material.bottomnavigation.BottomNavigationView import fr.iut.pm.movieapplication.R -import fr.iut.pm.movieapplication.ui.fragments.HomeSectionsFragment -import fr.iut.pm.movieapplication.ui.fragments.MoviesFragment +import fr.iut.pm.movieapplication.ui.fragments.* class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - loadFragments(HomeSectionsFragment(this)) + loadFragments(HomeSectionsFragment()) val navigationView = findViewById(R.id.navigation_view) navigationView.setOnItemSelectedListener { when(it.itemId) { R.id.home_page -> { - loadFragments(HomeSectionsFragment(this)) + loadFragments(HomeSectionsFragment()) return@setOnItemSelectedListener true } R.id.movies_page -> { - loadFragments(MoviesFragment(this)) + loadFragments(MoviesFragment()) + return@setOnItemSelectedListener true + } + + R.id.series_page -> { + loadFragments(TvShowsFragment()) + return@setOnItemSelectedListener true + } + R.id.favorites_page -> { + loadFragments(FavoritesFragment()) return@setOnItemSelectedListener true } else -> false } } - /* - //Trends fragment injected in main activity - val transaction = supportFragmentManager.beginTransaction() - transaction.replace(R.id.fragment_container, HomeSectionsFragment()) - transaction.addToBackStack(null) - transaction.commit() - */ - if(Build.VERSION.SDK_INT < 33) { - // Hide the status bar. - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN - // Remember that you should never show the action bar if the - // status bar is hidden, so hide that too if necessary. - actionBar?.hide() - } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.app_menu, menu) + val searchItem = menu?.findItem(R.id.search_bar) + val searchView : SearchView = searchItem?.actionView as SearchView + searchView.setOnQueryTextListener( object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + if(!query.isNullOrEmpty()) { + loadFragments(SearchResultFragment.newInstance(query)) + } + return true; + } + //A améliorer + override fun onQueryTextChange(query: String?): Boolean { + if(!query.isNullOrEmpty()) { + loadFragments(SearchResultFragment.newInstance(query)) + } + return true + } + }) + return true } private fun loadFragments(fragment: Fragment) { diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/CategoryItemDecoration.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/CategoryItemDecoration.kt deleted file mode 100644 index fe08862..0000000 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/CategoryItemDecoration.kt +++ /dev/null @@ -1,12 +0,0 @@ -package fr.iut.pm.movieapplication.ui.adapter - -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class CategoryItemDecoration : RecyclerView.ItemDecoration() { - - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - outRect.bottom = 50 - } -} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/HomeItemAdapter.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/HomeItemAdapter.kt deleted file mode 100644 index 2e6f389..0000000 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/HomeItemAdapter.kt +++ /dev/null @@ -1,29 +0,0 @@ -package fr.iut.pm.movieapplication.ui.adapter - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import androidx.recyclerview.widget.RecyclerView -import fr.iut.pm.movieapplication.R -import fr.iut.pm.movieapplication.ui.activity.MainActivity - -class HomeItemAdapter( - private val context : MainActivity, - private val layoutId : Int - ) : RecyclerView.Adapter() { - - class ViewHolder(view : View) : RecyclerView.ViewHolder(view) { - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater - .from(parent.context) - .inflate(layoutId, parent, false) - return ViewHolder(view) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) {} - - override fun getItemCount(): Int = 5 -} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MediaAdapter.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MediaAdapter.kt new file mode 100644 index 0000000..9e401c7 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MediaAdapter.kt @@ -0,0 +1,41 @@ +package fr.iut.pm.movieapplication.ui.adapter + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import coil.load +import fr.iut.pm.movieapplication.databinding.ItemHorizontalHomePageBinding +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.ui.interfaces.MediaSelection +import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection +import fr.iut.pm.movieapplication.utils.Constants + +class MediaAdapter(private val listener : MediaSelection): ListAdapter(DiffUtilMediaCallback) { + + private object DiffUtilMediaCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: MediaResult, newItem: MediaResult) = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: MediaResult, newItem: MediaResult) = oldItem == newItem + } + + class ViewHolder(private val binding : ItemHorizontalHomePageBinding, listener: MediaSelection) + : RecyclerView.ViewHolder(binding.root) { + + val mediaResult : MediaResult? get() = binding.mediaResult + + fun bind(mediaResult : MediaResult) { + binding.mediaResult = mediaResult + val imgUri = mediaResult.posterPath?.let { (Constants.IMG_URL +it).toUri().buildUpon().scheme("https").build() } + binding.itemImage.load(imgUri) + binding.executePendingBindings() + } + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder(ItemHorizontalHomePageBinding.inflate(LayoutInflater.from(parent.context)), listener) + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(getItem(position)) +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MovieAdapter.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MovieAdapter.kt new file mode 100644 index 0000000..20d7e3f --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/MovieAdapter.kt @@ -0,0 +1,45 @@ +package fr.iut.pm.movieapplication.ui.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import coil.load +import fr.iut.pm.movieapplication.databinding.ItemMovieCategoryBinding +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.ui.dialog.MovieDialog +import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection +import fr.iut.pm.movieapplication.utils.Constants + +class MovieAdapter(private val listener : MovieSelection) : ListAdapter(DiffUtilMovieCallback) { + + private object DiffUtilMovieCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Movie, newItem: Movie) = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: Movie, newItem: Movie) = oldItem == newItem + } + + class ViewHolder(private val binding : ItemMovieCategoryBinding, listener: MovieSelection) : + RecyclerView.ViewHolder(binding.root) { + + val movie : Movie? get() = binding.movie + + init { + itemView.setOnClickListener { + listener.onMovieSelected(movie?.id ?: 0) + } + } + fun bind(movie : Movie) { + binding.movie = movie + val imgUri = movie.posterPath?.let { (Constants.IMG_URL +it).toUri().buildUpon().scheme("https").build() } + binding.itemImage.load(imgUri) + binding.executePendingBindings() + } + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder(ItemMovieCategoryBinding.inflate(LayoutInflater.from(parent.context)), listener) + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(getItem(position)) +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/SearchResultAdapter.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/SearchResultAdapter.kt new file mode 100644 index 0000000..fe7b982 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/SearchResultAdapter.kt @@ -0,0 +1,42 @@ +package fr.iut.pm.movieapplication.ui.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.ListAdapter +import coil.load +import fr.iut.pm.movieapplication.databinding.ItemSearchResultsBinding +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.ui.interfaces.MediaSelection +import fr.iut.pm.movieapplication.utils.Constants + +class SearchResultAdapter(private val listener : MediaSelection) + : ListAdapter(DiffUtilMediaCallback) { + + private object DiffUtilMediaCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: MediaResult, newItem: MediaResult) = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: MediaResult, newItem: MediaResult) = oldItem == newItem + } + + class ViewHolder(private val binding : ItemSearchResultsBinding, listener: MediaSelection) + : RecyclerView.ViewHolder(binding.root) { + + val mediaResult : MediaResult? get() = binding.mediaResult + + fun bind(mediaResult : MediaResult) { + binding.mediaResult = mediaResult + val imgUri = mediaResult.posterPath?.let { (Constants.IMG_URL +it).toUri().buildUpon().scheme("https").build() } + binding.itemImage.load(imgUri) + binding.executePendingBindings() + } + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder(ItemSearchResultsBinding.inflate(LayoutInflater.from(parent.context)), listener) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(getItem(position)) + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/TvShowAdapter.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/TvShowAdapter.kt new file mode 100644 index 0000000..e139b65 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/adapter/TvShowAdapter.kt @@ -0,0 +1,48 @@ +package fr.iut.pm.movieapplication.ui.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import coil.load +import fr.iut.pm.movieapplication.databinding.ItemTvShowCategoryBinding +import fr.iut.pm.movieapplication.model.media.tvshow.TvShow +import fr.iut.pm.movieapplication.utils.Constants + +class TvShowAdapter() : ListAdapter(DiffUtilTvShowCallback) { + + + private object DiffUtilTvShowCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: TvShow, newItem: TvShow) = oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: TvShow, newItem: TvShow) = oldItem == newItem + } + + + class ViewHolder(private val binding : ItemTvShowCategoryBinding) + : RecyclerView.ViewHolder(binding.root){ + + val tvShow : TvShow? get() = binding.tvShow + + init { + itemView.setOnClickListener {} + } + + fun bind(tvShow : TvShow) { + binding.tvShow = tvShow + val imgUri = tvShow.posterPath?.let { (Constants.IMG_URL +it).toUri().buildUpon().scheme("https").build() } + binding.itemImage.load(imgUri) + binding.executePendingBindings() + } + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TvShowAdapter.ViewHolder = + TvShowAdapter.ViewHolder(ItemTvShowCategoryBinding.inflate(LayoutInflater.from(parent.context))) + + override fun onBindViewHolder(holder: TvShowAdapter.ViewHolder, position: Int) = holder.bind(getItem(position)) + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/dialog/MovieDialog.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/dialog/MovieDialog.kt new file mode 100644 index 0000000..ea5f0b1 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/dialog/MovieDialog.kt @@ -0,0 +1,47 @@ +package fr.iut.pm.movieapplication.ui.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.net.toUri +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels +import coil.load +import fr.iut.pm.movieapplication.R +import fr.iut.pm.movieapplication.databinding.MovieDialogBinding +import fr.iut.pm.movieapplication.ui.viewmodel.MoviesDialogVM +import fr.iut.pm.movieapplication.ui.viewmodel.MoviesDialogVMFactory +import fr.iut.pm.movieapplication.utils.Constants + +class MovieDialog : DialogFragment() { + + val moviesDialogVM by viewModels { MoviesDialogVMFactory(arguments?.getInt("movieId") ?: 0) } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = MovieDialogBinding.inflate(inflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.closeItem.setOnClickListener { dismiss() } + moviesDialogVM.getMovieDetailLiveData().observe(viewLifecycleOwner) { + binding.movieDetails = it + binding.detailsImage.load(it.posterPath?.let { it -> + (Constants.IMG_URL + it).toUri().buildUpon().scheme("https").build() + }) + } + + binding.registerMovieDetailsButton.setOnClickListener { + moviesDialogVM.register(binding.movieDetails!!) + } + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/FavoritesFragment.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/FavoritesFragment.kt new file mode 100644 index 0000000..97aed80 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/FavoritesFragment.kt @@ -0,0 +1,52 @@ +package fr.iut.pm.movieapplication.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import fr.iut.pm.movieapplication.databinding.FragmentFavoritesCategoryBinding +import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter +import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection +import fr.iut.pm.movieapplication.ui.viewmodel.FavoritesVM + +class FavoritesFragment : Fragment(), MovieSelection { + + private val favoritesVM by viewModels() + + private val movieAdapter = MovieAdapter(this); + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = FragmentFavoritesCategoryBinding.inflate(inflater) + + binding.favoritesVM = favoritesVM + binding.lifecycleOwner = viewLifecycleOwner + + with(binding.moviesItemRecyclerView) { + this.adapter = movieAdapter + } + + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + favoritesVM.getFavoritesLiveData().observe(viewLifecycleOwner) { + movieAdapter.submitList(it) + } + } + + + + + + override fun onMovieSelected(movieId: Int) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/HomeSectionsFragment.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/HomeSectionsFragment.kt index 4e47377..9c771dc 100644 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/HomeSectionsFragment.kt +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/HomeSectionsFragment.kt @@ -1,37 +1,73 @@ package fr.iut.pm.movieapplication.ui.fragments import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView -import fr.iut.pm.movieapplication.R +import androidx.fragment.app.viewModels +import fr.iut.pm.movieapplication.databinding.FragmentHomeSectionsBinding import fr.iut.pm.movieapplication.ui.activity.MainActivity -import fr.iut.pm.movieapplication.ui.adapter.HomeItemAdapter +import fr.iut.pm.movieapplication.ui.adapter.MediaAdapter import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration +import fr.iut.pm.movieapplication.ui.interfaces.MediaSelection +import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection +import fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM -class HomeSectionsFragment( - private val context : MainActivity -) : Fragment() { +class HomeSectionsFragment() : Fragment(), MediaSelection { - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_home_sections, container, false) + private val homeSectionsVM by viewModels() - //get the trends RecyclerView - val homeTrendsRecyclerView = view?.findViewById(R.id.home_trends_recycler_view) - homeTrendsRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page) - homeTrendsRecyclerView?.addItemDecoration(HomeItemDecoration()) + private val trendsAdapter = MediaAdapter(this) + private val popularMoviesAdapter = MediaAdapter(this) + private val popularTvShowsAdapter = MediaAdapter(this) - //get the popularity RecyclerView - val homePopularityRecyclerView = view?.findViewById(R.id.home_popularity_recycler_view) - homePopularityRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page) - homePopularityRecyclerView?.addItemDecoration(HomeItemDecoration()) + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?) + : View? { - //get the free RecyclerView - val homeFreeRecyclerView = view?.findViewById(R.id.home_free_recycler_view) - homeFreeRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page) - homeFreeRecyclerView?.addItemDecoration(HomeItemDecoration()) - return view + val binding = FragmentHomeSectionsBinding.inflate(inflater) + binding.homeSectionsVM = homeSectionsVM + binding.lifecycleOwner = viewLifecycleOwner + + with(binding.homeTrendsRecyclerView) { + adapter = trendsAdapter + addItemDecoration(HomeItemDecoration()) + } + + with(binding.homePopularMoviesRecyclerView) { + adapter = popularMoviesAdapter + addItemDecoration(HomeItemDecoration()) + } + + with(binding.homePopularTvShowsRecyclerView) { + adapter = popularTvShowsAdapter + addItemDecoration(HomeItemDecoration()) + } + + + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + homeSectionsVM.getTrendsLiveData().observe(viewLifecycleOwner) { + trendsAdapter.submitList(it) + } + + homeSectionsVM.getPopularMoviesLiveData().observe(viewLifecycleOwner) { + popularMoviesAdapter.submitList(it) + } + + homeSectionsVM.getPopularTvShowsLiveData().observe(viewLifecycleOwner) { + popularTvShowsAdapter.submitList(it) + } + } + override fun onMediaSelected(mediaId: Int) { + Log.d("ITEM SELECTED", mediaId.toString()) } } \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/MoviesFragment.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/MoviesFragment.kt index 648ac51..150badd 100644 --- a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/MoviesFragment.kt +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/MoviesFragment.kt @@ -4,30 +4,76 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.fragment.app.viewModels import fr.iut.pm.movieapplication.R -import fr.iut.pm.movieapplication.ui.activity.MainActivity -import fr.iut.pm.movieapplication.ui.adapter.CategoryItemDecoration -import fr.iut.pm.movieapplication.ui.adapter.HomeItemAdapter -import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration +import fr.iut.pm.movieapplication.databinding.FragmentMoviesBinding +import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter +import fr.iut.pm.movieapplication.ui.dialog.MovieDialog +import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection +import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVM class MoviesFragment( - private val context : MainActivity -) : Fragment() { +) : Fragment(), MovieSelection { + private val moviesVM by viewModels() + val moviesAdapter = MovieAdapter(this) + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = FragmentMoviesBinding.inflate(inflater) + binding.moviesVM = moviesVM + binding.lifecycleOwner = viewLifecycleOwner - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_movies, container, false) + with(binding.moviesItemRecyclerView) { + adapter = moviesAdapter + } - //get the recycler view - val moviesRecyclerView = view?.findViewById(R.id.movies_item_recycler_view) - moviesRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_vertical_fragment) - moviesRecyclerView?.layoutManager = GridLayoutManager(context, 2) - moviesRecyclerView?.addItemDecoration(CategoryItemDecoration()) + val adapter = ArrayAdapter.createFromResource( + requireContext(), + R.array.movie_filter, + android.R.layout.simple_spinner_item + ) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + with(binding.categoryMovieSpinner) + { + this.adapter = adapter + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + moviesVM.getData(selectedItem.toString()) + } - return view + + override fun onNothingSelected(parent: AdapterView<*>?) { + TODO("Not yet implemented") + } + + } + } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + moviesVM.getMoviesLiveData().observe(viewLifecycleOwner) { + moviesAdapter.submitList(it) + } + } + + override fun onMovieSelected(movieId: Int) { + val dialog = MovieDialog() + val args = Bundle() + args.putInt("movieId",movieId) + dialog.arguments = args + dialog.show(parentFragmentManager, "tag") } -} \ No newline at end of file +} diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/SearchResultFragment.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/SearchResultFragment.kt new file mode 100644 index 0000000..139da97 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/SearchResultFragment.kt @@ -0,0 +1,60 @@ +package fr.iut.pm.movieapplication.ui.fragments + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import fr.iut.pm.movieapplication.databinding.FragmentSearchResultsBinding +import fr.iut.pm.movieapplication.ui.adapter.MediaAdapter +import fr.iut.pm.movieapplication.ui.adapter.SearchResultAdapter +import fr.iut.pm.movieapplication.ui.interfaces.MediaSelection +import fr.iut.pm.movieapplication.ui.viewmodel.SearchResultVM +import fr.iut.pm.movieapplication.ui.viewmodel.SearchResultVMFactory + +class SearchResultFragment : Fragment(), MediaSelection { + + private val searchResultViewModel by viewModels { SearchResultVMFactory(arguments?.getString("query")!!) } + private val searchResultAdapter = SearchResultAdapter(this) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = FragmentSearchResultsBinding.inflate(inflater, container, false) + + binding.lifecycleOwner = viewLifecycleOwner + binding.searchResultsVM = searchResultViewModel + + with(binding.searchResultsItemRecyclerView) { + this.adapter = searchResultAdapter + } + + return binding.root; + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + searchResultViewModel.getSearchResultLiveData().observe(viewLifecycleOwner) { + searchResultAdapter.submitList(it) + } + + } + + override fun onMediaSelected(mediaId: Int) { + Log.d("ITEM SELECTED", mediaId.toString()) + } + + companion object { + fun newInstance(query: String): SearchResultFragment { + val fragment = SearchResultFragment() + val args = Bundle() + args.putString("query", query) + fragment.arguments = args + return fragment + } + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/TvShowsFragment.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/TvShowsFragment.kt new file mode 100644 index 0000000..fe13393 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/fragments/TvShowsFragment.kt @@ -0,0 +1,68 @@ +package fr.iut.pm.movieapplication.ui.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import fr.iut.pm.movieapplication.R +import fr.iut.pm.movieapplication.databinding.FragmentTvShowsBinding +import fr.iut.pm.movieapplication.ui.activity.MainActivity +import fr.iut.pm.movieapplication.ui.viewmodel.TvShowVM + +class TvShowsFragment( +) : Fragment() { + + + private val tvShowVM by viewModels() + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?) + : View { + val binding = FragmentTvShowsBinding.inflate(inflater) + + binding.tvShowVM = tvShowVM + binding.lifecycleOwner = viewLifecycleOwner + + val adapter = ArrayAdapter.createFromResource( + requireContext(), + R.array.tv_show_filter, + android.R.layout.simple_spinner_item + ) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + + with(binding.categoryTvShowSpinner) + { + this.adapter = adapter + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + tvShowVM.getData(selectedItem.toString()) + } + + + override fun onNothingSelected(parent: AdapterView<*>?) { + TODO("Not yet implemented") + } + + } + } + + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + tvShowVM.getTvShowLiveData().observe(viewLifecycleOwner) { + tvShowVM.tvShowAdapter.submitList(it) + } + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MediaSelection.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MediaSelection.kt new file mode 100644 index 0000000..fdff9d6 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MediaSelection.kt @@ -0,0 +1,6 @@ +package fr.iut.pm.movieapplication.ui.interfaces + +interface MediaSelection { + + fun onMediaSelected(mediaId : Int) +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MovieSelection.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MovieSelection.kt new file mode 100644 index 0000000..a195ecb --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/interfaces/MovieSelection.kt @@ -0,0 +1,6 @@ +package fr.iut.pm.movieapplication.ui.interfaces + +interface MovieSelection { + + fun onMovieSelected(movieId : Int) +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/FavoritesVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/FavoritesVM.kt new file mode 100644 index 0000000..5667f7d --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/FavoritesVM.kt @@ -0,0 +1,30 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import fr.iut.pm.movieapplication.data.database.MovieDataBase +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.repository.local.MovieLocalRepository +import kotlinx.coroutines.launch + +class FavoritesVM : ViewModel() { + + private val repository = MovieLocalRepository(MovieDataBase.getInstance().movieDAO()) + + private var _favoritesLiveData : MutableLiveData> = MutableLiveData() + + fun getFavoritesLiveData() : MutableLiveData> = _favoritesLiveData + + + init { + //with dispatchers.IO + viewModelScope.launch{ + _favoritesLiveData.value = repository.getAllMovies() + } + + + + } + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/HomeSectionsVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/HomeSectionsVM.kt new file mode 100644 index 0000000..70a9e9c --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/HomeSectionsVM.kt @@ -0,0 +1,33 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.repository.api.MediaRepository +import kotlinx.coroutines.launch + +class HomeSectionsVM : ViewModel() { + + private val repository = MediaRepository() + + private var _trendsLiveData : MutableLiveData> = MutableLiveData() + fun getTrendsLiveData() : MutableLiveData> = _trendsLiveData + + private var _popularMoviesLiveData : MutableLiveData> = MutableLiveData() + fun getPopularMoviesLiveData() : MutableLiveData> = _popularMoviesLiveData + + private var _popularTvShowsLiveData : MutableLiveData> = MutableLiveData() + fun getPopularTvShowsLiveData() : MutableLiveData> = _popularTvShowsLiveData + + + init { + viewModelScope.launch { + _trendsLiveData.postValue(repository.getTrends()) + _popularMoviesLiveData.postValue(repository.getPopularMovies()) + _popularTvShowsLiveData.postValue(repository.getPopularTvShows()) + } + } + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesDialogVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesDialogVM.kt new file mode 100644 index 0000000..cc75ade --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesDialogVM.kt @@ -0,0 +1,39 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.* +import fr.iut.pm.movieapplication.data.database.MovieDataBase +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails +import fr.iut.pm.movieapplication.repository.api.MovieAPIRepository +import fr.iut.pm.movieapplication.repository.local.MovieLocalRepository +import kotlinx.coroutines.launch + +class MoviesDialogVM(private val movieId : Int) : ViewModel() { + + private val repository = MovieAPIRepository() + + private val movieLocalRepository = MovieLocalRepository( + MovieDataBase.getInstance().movieDAO()) + + private var _movieDetailsLiveData : MutableLiveData = MutableLiveData() + fun getMovieDetailLiveData() : LiveData = _movieDetailsLiveData + + init { + viewModelScope.launch { + if(movieId != 0) _movieDetailsLiveData.postValue(repository.getMovieDetails(movieId)) + } + } + + fun register(movieDetails: MovieDetails) = viewModelScope.launch { + + movieLocalRepository.insertMovieDetails(movieDetails) + } + +} + +class MoviesDialogVMFactory(private val movieId : Int) : ViewModelProvider.Factory +{ + override funcreate(modelClass:Class) : T + { + return MoviesDialogVM(movieId) as T + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesVM.kt new file mode 100644 index 0000000..b0016cd --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/MoviesVM.kt @@ -0,0 +1,109 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.* +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.repository.api.MovieAPIRepository +import fr.iut.pm.movieapplication.utils.Constants.Companion.MAX_PAGE +import kotlinx.coroutines.launch + +class MoviesVM() : ViewModel() { + /** + * The movie repository used to get our data + */ + private val repository = MovieAPIRepository() + + /** + * The MutableLiveData + */ + private var _moviesLiveData : MutableLiveData> = MutableLiveData>() + + /** + * Getter of the LiveData + */ + fun getMoviesLiveData() : LiveData> = _moviesLiveData + + /** + * The current data filter + */ + private var currentFilter = "" + + /** + * The adapter of the RecyclerView (set on the RecyclerView in the view) + */ + + + val scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + val layoutManager = recyclerView.layoutManager as GridLayoutManager + val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() + val totalItemCount = layoutManager.itemCount + + if(lastVisibleItemPosition == totalItemCount -1) { + + ++currentPage + //1000 is the MAX_PAGE + if(currentPage <= MAX_PAGE) getMoreData(currentPage) + } + } + } + + /** + * Currrent page where the data are obtained + */ + private var currentPage = 1 + + /** + * Get the data with a given filter + * @param filter filter applied to get data + */ + fun getData(filter : String) { + currentFilter = filter + currentPage = 1 + when(currentFilter) { + "Populaires" -> viewModelScope.launch { + _moviesLiveData.postValue(repository.getPopularMovies()) + repository.getMovieDetails(315162) + } + "Du moment" -> viewModelScope.launch { + _moviesLiveData.postValue(repository.getNowPlayingMovies()) + } + "À venir" -> viewModelScope.launch { + _moviesLiveData.postValue(repository.getUpcomingMovies()) + } + "Les mieux notés" -> viewModelScope.launch { + _moviesLiveData.postValue(repository.getTopRatedMovies()) + } + } + + } + + /** + * Get more data with the actual filter + * @param page page from which the data are obtained + */ + private fun getMoreData(page : Int = 1) { + var movies : List + when(currentFilter) { + "Populaires" -> viewModelScope.launch { + movies = _moviesLiveData.value?.plus(repository.getPopularMovies(page)) ?: listOf() + _moviesLiveData.postValue(movies) + } + "Du moment" -> viewModelScope.launch { + movies = _moviesLiveData.value?.plus(repository.getNowPlayingMovies(page)) ?: listOf() + _moviesLiveData.postValue(movies) + } + "À venir" -> viewModelScope.launch { + movies = _moviesLiveData.value?.plus(repository.getUpcomingMovies(page)) ?: listOf() + _moviesLiveData.postValue(movies) + } + "Les mieux notés" -> viewModelScope.launch { + movies = _moviesLiveData.value?.plus(repository.getTopRatedMovies(page)) ?: listOf() + _moviesLiveData.postValue(movies) + } + } + + } + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/SearchResultVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/SearchResultVM.kt new file mode 100644 index 0000000..a0c2c15 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/SearchResultVM.kt @@ -0,0 +1,32 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.* +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.repository.api.MediaRepository +import kotlinx.coroutines.launch + +class SearchResultVM(private var query : String) : ViewModel() { + + private val repository : MediaRepository = MediaRepository() + + private var _queryLiveData : MutableLiveData = MutableLiveData() + fun getQueryLiveData() : LiveData = _queryLiveData + + private var _searchResultLiveData : MutableLiveData> = MutableLiveData() + fun getSearchResultLiveData() : LiveData> = _searchResultLiveData + + init { + _queryLiveData.value = query + viewModelScope.launch { + _searchResultLiveData.postValue(repository.search(query)) + } + } +} + +class SearchResultVMFactory(private var query : String) : ViewModelProvider.Factory +{ + override funcreate(modelClass:Class) : T + { + return SearchResultVM(query) as T + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/TvShowVM.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/TvShowVM.kt new file mode 100644 index 0000000..986a083 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/ui/viewmodel/TvShowVM.kt @@ -0,0 +1,43 @@ +package fr.iut.pm.movieapplication.ui.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import fr.iut.pm.movieapplication.model.media.tvshow.TvShow +import fr.iut.pm.movieapplication.repository.api.TvShowRepository +import fr.iut.pm.movieapplication.ui.adapter.TvShowAdapter +import kotlinx.coroutines.launch + +class TvShowVM : ViewModel() { + + private val tvShowRepository = TvShowRepository() + + private var tvShowLiveData : MutableLiveData> = MutableLiveData>() + fun getTvShowLiveData() : LiveData> = tvShowLiveData + + private var currentFilter : String = "" + val tvShowAdapter = TvShowAdapter() + + private var currentPage = 1 + + fun getData(filter : String) { + + currentFilter = filter + currentPage = 1 + when(currentFilter) { + "Populaires" -> viewModelScope.launch { + tvShowLiveData.postValue(tvShowRepository.getPopularTvShows()) + } + "Diffusées aujourd\'hui" -> viewModelScope.launch { + tvShowLiveData.postValue(tvShowRepository.getAiringTodayTvShows()) + } + "En cours de diffusion" -> viewModelScope.launch { + tvShowLiveData.postValue(tvShowRepository.getTvOnTheAirTvShows()) + } + "Les mieux notées" -> viewModelScope.launch { + tvShowLiveData.postValue(tvShowRepository.getTopRatedTvShows()) + } + } + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/Constants.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/Constants.kt new file mode 100644 index 0000000..37e050b --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/Constants.kt @@ -0,0 +1,17 @@ +package fr.iut.pm.movieapplication.utils + +class Constants { + + companion object { + + //API + const val BASE_URL = "https://api.themoviedb.org/3/" + const val IMG_URL = "https://image.tmdb.org/t/p/w500" + const val API_KEY = "8f14a279249638d7f247d0d7298b21b4" + + + //VIEW PAGINATION + const val PAGE_SIZE = 20 + const val MAX_PAGE = 1000 + } +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MediaResultMapper.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MediaResultMapper.kt new file mode 100644 index 0000000..47b5642 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MediaResultMapper.kt @@ -0,0 +1,103 @@ +package fr.iut.pm.movieapplication.utils + +import fr.iut.pm.movieapplication.api.dtos.GenreDTO +import fr.iut.pm.movieapplication.api.dtos.MediaResultDTO +import fr.iut.pm.movieapplication.api.dtos.MovieDetailsDTO +import fr.iut.pm.movieapplication.model.Genre +import fr.iut.pm.movieapplication.model.media.MediaResult +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails +import fr.iut.pm.movieapplication.model.media.tvshow.TvShow + +object MediaResultMapper { + + fun mapToTvShow(mediaResultDTO: MediaResultDTO): TvShow { + return TvShow( + posterPath = mediaResultDTO.posterPath, + popularity = mediaResultDTO.popularity!!, + id = mediaResultDTO.id, + backdropPath = mediaResultDTO.backdropPath ?: "", + voteAverage = mediaResultDTO.voteAverage!!, + overview = mediaResultDTO.overview ?: "", + firstAirDate = mediaResultDTO.firstAirDate, + originCountry = mediaResultDTO.originCountry!!, +// genreIds = mediaResultDTO.genreIds, + originalLanguage = mediaResultDTO.originalLanguage!!, + voteCount = mediaResultDTO.voteCount!!, + name = mediaResultDTO.name!!, + originalName = mediaResultDTO.originalName!! + ) + } + + fun mapToMovie(mediaResultDTO: MediaResultDTO): Movie { + return Movie( + posterPath = mediaResultDTO.posterPath, + adult = mediaResultDTO.adult, + overview = mediaResultDTO.overview ?: "", + releaseDate = mediaResultDTO.releaseDate ?: "", +// genreIds = mediaResultDTO.genreIds, + id = mediaResultDTO.id, + originalTitle = mediaResultDTO.originalTitle!!, + originalLanguage = mediaResultDTO.originalLanguage!!, + title = mediaResultDTO.title!!, + backdropPath = mediaResultDTO.backdropPath, + popularity = mediaResultDTO.popularity!!, + voteCount = mediaResultDTO.voteCount!!, + //video = mediaResultDTO.video, + voteAverage = mediaResultDTO.voteAverage!! + ) + } + + fun mapToMediaResult(mediaResultDTO: MediaResultDTO) : MediaResult { + return MediaResult( + posterPath = mediaResultDTO.posterPath, + adult = mediaResultDTO.adult, + overview = mediaResultDTO.overview ?: "", + releaseDate = mediaResultDTO.releaseDate ?: mediaResultDTO.firstAirDate!! , + originCountry = mediaResultDTO.originCountry, +// genreIds = mediaResultDTO.genreIds, + id = mediaResultDTO.id, + originalTitle = mediaResultDTO.originalTitle ?: mediaResultDTO.originalName!!, //if it's not a movie also it's a tvshow + originalLanguage = mediaResultDTO.originalLanguage!!, + title = mediaResultDTO.title ?: mediaResultDTO.name!!, //if it's not a movie also it's a tvshow + backdropPath = mediaResultDTO.backdropPath, + popularity = mediaResultDTO.popularity!!, + voteCount = mediaResultDTO.voteCount!!, + voteAverage = mediaResultDTO.voteAverage!!, + mediaType = mediaResultDTO.mediaType + ) + } + + fun mapToMovieDetails(movieDetailsDTO: MovieDetailsDTO?): MovieDetails? { + if(movieDetailsDTO == null) return null + return MovieDetails( + posterPath = movieDetailsDTO.posterPath, + adult = movieDetailsDTO.adult!!, + overview = movieDetailsDTO.overview ?: "", + releaseDate = movieDetailsDTO.releaseDate ?: "", + id = movieDetailsDTO.id, + originalTitle = movieDetailsDTO.originalTitle!!, + originalLanguage = movieDetailsDTO.originalLanguage, + title = movieDetailsDTO.title, + backdropPath = movieDetailsDTO.backdropPath, + popularity = movieDetailsDTO.popularity, + voteCount = movieDetailsDTO.voteCount, + voteAverage = movieDetailsDTO.voteAverage, + budget = movieDetailsDTO.budget, + genres = movieDetailsDTO.genres.map { mapGenreDTOToGenre(it) }, + homepage = movieDetailsDTO.homepage, + revenue = movieDetailsDTO.revenue, + status = movieDetailsDTO.status + ) + + } + + fun mapGenreDTOToGenre(genreDTO : GenreDTO) : Genre { + return Genre( + name = genreDTO.name, + id = genreDTO.id + ) + } + + +} \ No newline at end of file diff --git a/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MovieMapper.kt b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MovieMapper.kt new file mode 100644 index 0000000..0801222 --- /dev/null +++ b/Sources/app/src/main/java/fr/iut/pm/movieapplication/utils/MovieMapper.kt @@ -0,0 +1,48 @@ +package fr.iut.pm.movieapplication.utils + +import fr.iut.pm.movieapplication.api.dtos.MovieDTO +import fr.iut.pm.movieapplication.api.dtos.MovieDetailsDTO +import fr.iut.pm.movieapplication.model.media.movie.Movie +import fr.iut.pm.movieapplication.model.media.movie.MovieDetails + +object MovieMapper { + + fun mapToMovie(movieDTO : MovieDTO) : Movie { + return Movie( + posterPath = movieDTO.posterPath, + adult = movieDTO.adult, + overview = movieDTO.overview, + releaseDate = movieDTO.releaseDate, + id = movieDTO.id, + originalTitle = movieDTO.originalTitle, + originalLanguage = movieDTO.originalLanguage, + title = movieDTO.title, + backdropPath = movieDTO.backdropPath, + popularity = movieDTO.popularity, + voteCount = movieDTO.voteCount, + voteAverage = movieDTO.voteAverage + ) + } + + fun mapToMovieDetails(movieDetailsDTO: MovieDetailsDTO ) : MovieDetails { + return MovieDetails( + posterPath = movieDetailsDTO.posterPath, + adult = movieDetailsDTO.adult!!, + overview = movieDetailsDTO.overview ?: "", + releaseDate = movieDetailsDTO.releaseDate ?: "", + id = movieDetailsDTO.id, + originalTitle = movieDetailsDTO.originalTitle, + originalLanguage = movieDetailsDTO.originalLanguage, + title = movieDetailsDTO.title, + backdropPath = movieDetailsDTO.backdropPath, + popularity = movieDetailsDTO.popularity, + voteCount = movieDetailsDTO.voteCount, + voteAverage = movieDetailsDTO.voteAverage, + budget = movieDetailsDTO.budget, + genres = movieDetailsDTO.genres.map { MediaResultMapper.mapGenreDTOToGenre(it) }, + homepage = movieDetailsDTO.homepage, + revenue = movieDetailsDTO.revenue, + status = movieDetailsDTO.status + ) + } +} \ No newline at end of file diff --git a/Sources/app/src/main/res/drawable/ic_close.xml b/Sources/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..844b6b6 --- /dev/null +++ b/Sources/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,5 @@ + + + diff --git a/Sources/app/src/main/res/drawable/item_selector.xml b/Sources/app/src/main/res/drawable/item_selector.xml new file mode 100644 index 0000000..1666c03 --- /dev/null +++ b/Sources/app/src/main/res/drawable/item_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/font/source_sans_pro.ttf b/Sources/app/src/main/res/font/source_sans_pro.ttf new file mode 100644 index 0000000..6791613 Binary files /dev/null and b/Sources/app/src/main/res/font/source_sans_pro.ttf differ diff --git a/Sources/app/src/main/res/layout/activity_main.xml b/Sources/app/src/main/res/layout/activity_main.xml index d0fbc8a..b98f75d 100644 --- a/Sources/app/src/main/res/layout/activity_main.xml +++ b/Sources/app/src/main/res/layout/activity_main.xml @@ -11,7 +11,7 @@ android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginBottom="75dp" + android:layout_marginBottom="55dp" app:layout_constraintBottom_toTopOf="@+id/navigation_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -20,10 +20,14 @@ \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/fragment_favorites_category.xml b/Sources/app/src/main/res/layout/fragment_favorites_category.xml new file mode 100644 index 0000000..424be29 --- /dev/null +++ b/Sources/app/src/main/res/layout/fragment_favorites_category.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/fragment_home_sections.xml b/Sources/app/src/main/res/layout/fragment_home_sections.xml index 6c2afed..b0a4d4a 100644 --- a/Sources/app/src/main/res/layout/fragment_home_sections.xml +++ b/Sources/app/src/main/res/layout/fragment_home_sections.xml @@ -1,59 +1,86 @@ + - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/fragment_movies.xml b/Sources/app/src/main/res/layout/fragment_movies.xml index 8b0c3d2..85d6b50 100644 --- a/Sources/app/src/main/res/layout/fragment_movies.xml +++ b/Sources/app/src/main/res/layout/fragment_movies.xml @@ -1,13 +1,55 @@ - - + + + + + + + + + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/fragment_search_results.xml b/Sources/app/src/main/res/layout/fragment_search_results.xml new file mode 100644 index 0000000..4fdad62 --- /dev/null +++ b/Sources/app/src/main/res/layout/fragment_search_results.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/fragment_tv_shows.xml b/Sources/app/src/main/res/layout/fragment_tv_shows.xml new file mode 100644 index 0000000..550b066 --- /dev/null +++ b/Sources/app/src/main/res/layout/fragment_tv_shows.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/item_horizontal_home_page.xml b/Sources/app/src/main/res/layout/item_horizontal_home_page.xml index d7dc465..86db216 100644 --- a/Sources/app/src/main/res/layout/item_horizontal_home_page.xml +++ b/Sources/app/src/main/res/layout/item_horizontal_home_page.xml @@ -1,46 +1,51 @@ - - - - - + + + + + + + + - - - - - - - - - \ No newline at end of file + app:cardCornerRadius="5dp"> + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/item_movie_category.xml b/Sources/app/src/main/res/layout/item_movie_category.xml new file mode 100644 index 0000000..9b7af0a --- /dev/null +++ b/Sources/app/src/main/res/layout/item_movie_category.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/item_search_results.xml b/Sources/app/src/main/res/layout/item_search_results.xml new file mode 100644 index 0000000..2f7c485 --- /dev/null +++ b/Sources/app/src/main/res/layout/item_search_results.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/item_tv_show_category.xml b/Sources/app/src/main/res/layout/item_tv_show_category.xml new file mode 100644 index 0000000..8f58121 --- /dev/null +++ b/Sources/app/src/main/res/layout/item_tv_show_category.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/item_vertical_fragment.xml b/Sources/app/src/main/res/layout/item_vertical_fragment.xml deleted file mode 100644 index a07abbd..0000000 --- a/Sources/app/src/main/res/layout/item_vertical_fragment.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Sources/app/src/main/res/layout/movie_dialog.xml b/Sources/app/src/main/res/layout/movie_dialog.xml new file mode 100644 index 0000000..c8d5dbc --- /dev/null +++ b/Sources/app/src/main/res/layout/movie_dialog.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + +