Compare commits

...

24 Commits

Author SHA1 Message Date
Jordan ARTZET f5e4042230 Merge remote-tracking branch 'origin/master'
2 years ago
Jordan ARTZET 6d3457a8cb 🔐 api key is hide
2 years ago
Jordan ARTZET b4022f79ad 📝 update readme
2 years ago
Jordan ARTZET 7636cd89b7 ♻️ change color and refactor some code
2 years ago
Jordan ARTZET cd12188a75 🚧 resize the home menu
2 years ago
Jordan ARTZET 399941f2d0 add the click in favorites (not definitive)
2 years ago
Jordan ARTZET 1a6dfbc47c block screen rotation
2 years ago
Jordan ARTZET 8be6142404 📝 update readme
2 years ago
Jordan ARTZET a992dcd64f 🔀merge branch 'develop' -> 'master'
2 years ago
Jordan ARTZET 95750999e5 add the search features
2 years ago
Jordan ARTZET 0fd619f41b 📝 add documentation
2 years ago
Jordan ARTZET ba977653c0 🐛 fix bug movies were not inserted
2 years ago
Jordan ARTZET d0768d2704 🚧 add the room database
2 years ago
Jordan ARTZET 10646b7c38 🔀 merge branch 'features/api/requests/2' into develop
2 years ago
Jordan ARTZET bf50751c6f 🚧 add bindings on the home page
2 years ago
Jordan ARTZET 797ce00665 🚧 some requests implemented need to had coroutines now to improve perf
2 years ago
Jordan ARTZET 76130b9358 🚧 prepare class for the movie details request
2 years ago
Jordan ARTZET 724e6cb75c 🚧 add the tv show request and display the data in the tvshow cateogry
2 years ago
Jordan ARTZET 635a458cad :recycler: refactor and clean the code
2 years ago
Jordan ARTZET e29247f3ce 🚧 add data binding and view model
2 years ago
Jordan ARTZET fc371bf386 🚧 add some requests
2 years ago
Jordan ARTZET 5a6b157c4f 🚧 rebuild the dtos correctly
2 years ago
Jordan ARTZET ddc0f661e6 🚧 add tvdto and some requests
2 years ago
Jordan ARTZET 96e30ea2a5 Merge branch 'features/retrofit/1' into develop
2 years ago

@ -2,7 +2,10 @@
## Overview
___
Description of my project
This project use the API of [The Movie Database](https://www.themoviedb.org/?language=fr)
This project is still in development :construction:
## Documentation
___
@ -19,7 +22,17 @@ Here are the prerequisites before launching the project.
| ---- | ---- |
| The integrated Development Environment (IDE) Android Studios |<img src="Documentation/images/Android_Studio_Trademark.png" alt="drawing" width="95" height="30" /> |
### Dependencies
- AndroidX
- Retrofit2 to use the api requests and moshi to parse data
- Coil for load the images
- Room Database to store local data
- The databinding
- JUnit is included too because i want to add tests on the project in the future
### Installation
To launch the project, open `Android Studios` and click on `file` on the left corner.
Click on `open` and open the `Sources` folder in the project.
Click on `open` and open the `Sources` folder in the project and let the IDE index the files

@ -8,7 +8,14 @@ android {
namespace 'fr.iut.pm.movieapplication'
compileSdk 33
def apikeyPropertiesFile = rootProject.file("apikey.properties")
def apikeyProperties = new Properties()
apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
defaultConfig {
buildConfigField("String", "API_KEY", apikeyProperties['API_KEY'])
applicationId "fr.iut.pm.movieapplication"
minSdk 16
targetSdk 33
@ -36,24 +43,29 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
}
}
dependencies {
implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion"
implementation "androidx.activity:activity-ktx:$rootProject.activityVersion"
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"
@ -64,19 +76,14 @@ dependencies {
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"
//GSON
//implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
// Retrofit
// Retrofit & Moshi
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Scalar Converter
//implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
// Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
// Testing

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MovieApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -13,9 +14,10 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MovieApplication"
tools:targetApi="31">
tools:targetApi="33">
<activity
android:name=".ui.activity.MainActivity"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

@ -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)
}
}

@ -1,22 +1,57 @@
package fr.iut.pm.movieapplication.api
import fr.iut.pm.movieapplication.api.dtos.MovieResultDTO
import fr.iut.pm.movieapplication.api.dtos.PopularDTO
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.Call
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface MovieApplicationAPI {
// Movie
@GET("movie/popular")
fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY) : Call<PopularDTO>
suspend fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response<ResultDTO>
@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<ResultDTO>
@GET("movie/upcoming")
suspend fun getUpcomingMovies(@Query("api_key") apiKey: String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response<ResultDTO>
@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<ResultDTO>
// 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<MovieDetailsDTO>
// 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<ResultDTO>
@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<ResultDTO>
@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<ResultDTO>
@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<ResultDTO>
@GET("trending/{media_type}/{time_window}")
fun getTrending(@Path("media_type") mediaType : String = "all", @Path("time_window") timeWindow : String = "day", @Query("api_key") apiKey: String = API_KEY ) : Call<PopularDTO>
suspend fun getTrending(@Path("media_type") mediaType : String = "all", @Path("time_window") timeWindow : String = "day", @Query("api_key") apiKey: String = API_KEY ) : Response<ResultDTO>
@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<ResultDTO>
@GET("movie/{movie_id")
fun getMovieDetails(@Path("movie_id") movieId : Int, @Query("api_key") apiKey: String = API_KEY) : Call<MovieResultDTO>
@GET("tv/{tv_id}")
fun getShowDetails(@Path("tv_id") tvId : Int, @Query("api_key") apiKey: String = API_KEY)
}

@ -0,0 +1,8 @@
package fr.iut.pm.movieapplication.api.dtos
data class GenreDTO(
val id : Int,
val name : String
) {
}

@ -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<String>? = null,
// @Json(name = "genre_ids")
// val genreIds : List<Int>,
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
) {
}

@ -1,44 +1,29 @@
package fr.iut.pm.movieapplication.api.dtos
import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.PrimaryKey
import androidx.room.Relation
import com.squareup.moshi.Json
import fr.iut.pm.movieapplication.model.Genre
import fr.iut.pm.movieapplication.model.ProductionCompany
import fr.iut.pm.movieapplication.model.ProductionCountry
import fr.iut.pm.movieapplication.network.dtos.GenreDTO
class MovieDTO (
val adult: Boolean,
val budget: Int,
val genres: Array<GenreDTO>?,
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,
open class MovieDTO (
@Json(name = "poster_path")
val posterPath: String?,
@Json(name = "production_countries")
val productionCountries: Array<ProductionCountry>,
open val posterPath: String?,
open val adult: Boolean,
open val overview: String?,
@Json(name = "release_date")
val releaseDate: String,
val revenue: Int,
val runtime: Int?,
//var spokenLanguages : Array<SpokenLanguage>,
val status: String,
@Json(name = "tag_line")
val tagLine: String?,
val title: String,
@Json(name = "vote_average")
val voteAverage: Double,
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")
val voteCount: Int,
val backdropPath: String?
open val voteCount: Int,
@Json(name = "vote_average")
open val voteAverage: Double
){
}

@ -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<GenreDTO>,
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
)
{}

@ -28,7 +28,6 @@ class MovieResultDTO(
val popularity : Double?,
@Json(name = "vote_count")
val voteCount : Int?,
val video : Boolean?,
@Json(name = "vote_average")
val voteAverage : Double?

@ -5,11 +5,11 @@ import com.squareup.moshi.Json
class PopularDTO(
class ResultDTO(
@Json(name = "page")
val page : Int,
@Json(name = "results")
val results : List<MovieResultDTO>,
val results : List<MediaResultDTO>,
@Json(name = "total_results")
val totalResults : Int,
@Json(name = "total_pages")

@ -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<String>,
@Json(name = "genre_ids")
open val genreIds: List<Int>,
@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,
)
{}

@ -1,23 +1,61 @@
package fr.iut.pm.movieapplication.data.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
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<List<Movie>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(movie : Movie)
suspend fun insert(movieEntity : MovieEntity)
/**
* Get all movies from the movies_table
*/
@Query("SELECT * FROM movies_table")
suspend fun getAllMovies() : List<MovieEntity>
/**
* Get all movies from the movies_details_table
*/
@Query("SELECT * FROM movies_details_table")
suspend fun getAllMoviesDetails() : List<MovieDetailsEntity>
/**
* 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<GenreEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMovieDetailsGenres(movieDetailsGenresEntity : List<MovieDetailsGenreEntity>)
@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)
}
@Query("DELETE FROM movies_table")
suspend fun deleteAll()
@Transaction
@Query("SELECT * FROM movies_details_table WHERE movie_id = :movieId")
suspend fun getMovieDetailsWithGenresById(movieId: Int): MovieDetailsWithGenres
}

@ -1,9 +0,0 @@
package fr.iut.pm.movieapplication.data.dao
import androidx.room.Entity
@Entity("movies_table")
class MovieEntity {
}

@ -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
}
}
}

@ -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
) {
}

@ -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
) {
}

@ -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
)

@ -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<GenreEntity>
)

@ -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
) {
}

@ -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<Genre>) : List<GenreEntity> {
return genres.map { mapToGenreEntity(it) }
}
/**
* Map a [GenreEntity] to a [Genre]
*/
fun mapToGenre(genreEntity : GenreEntity) : Genre {
return Genre(
id = genreEntity.genreId,
name = genreEntity.name
)
}
}

@ -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) }
)
}
}

@ -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 {

@ -1,64 +0,0 @@
package fr.iut.pm.movieapplication.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@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<Genre>?,
@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: Double?,
@ColumnInfo(name = "poster_path")
val posterPath: String?,
val productionCompanies: Array<ProductionCompany>?,
@ColumnInfo(name = "production_countries")
val productionCountries: Array<ProductionCountry>?,
@ColumnInfo(name = "release_date")
val releaseDate: String?,
val revenue: Int?,
val runtime: Int?,
//var spokenLanguages : Array<SpokenLanguage>,
val status: String?,
@ColumnInfo(name = "tag_line")
val tagLine: String?,
val title: String?,
@ColumnInfo(name = "vote_average")
val voteAverage: Double?,
@ColumnInfo(name = "vote_count")
val voteCount: Int?,
val backdropPath: String?
) {
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
}
}

@ -2,13 +2,14 @@ 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<Movie>,
val results : List<MovieDetails>,
val totalResults : Int,
val totalPages : Int

@ -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<String>? = null,
// val genreIds: List<Int>,
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?
)
{}

@ -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
}
}

@ -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<Genre>,
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)
{
}

@ -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<String>,
// open val genreIds: List<Int>,
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
}
}

@ -0,0 +1,4 @@
package fr.iut.pm.movieapplication.model.media.tvshow
class TvShowDetails {
}

@ -1,7 +0,0 @@
package fr.iut.pm.movieapplication.network.dtos
data class GenreDTO(
private val id : Int,
private val name : String
) {
}

@ -1,66 +0,0 @@
package fr.iut.pm.movieapplication.repository
import android.util.Log
import fr.iut.pm.movieapplication.api.RetrofitInstance
import fr.iut.pm.movieapplication.api.dtos.PopularDTO
import fr.iut.pm.movieapplication.model.Movie
import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY
import fr.iut.pm.movieapplication.utils.Mapper
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MovieRepository() {
fun getPopularMovies(callback: (List<Movie>) -> Unit ) {
val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getPopularMovies().enqueue(object :
Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if (response.isSuccessful) {
Log.d("List :", response.body().toString())
val popularDTO = response.body()
val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach {
val movie = Mapper.MapToMovie(it)
listMovie.add(movie)
Log.d("Movie ", movie.title!!)
}
}
callback(listMovie)
}
override fun onFailure(call: Call<PopularDTO>, t: Throwable) {
Log.d("Error failure", t.message.toString())
}
})
}
fun getTrends(callback: (List<Movie>) -> Unit) {
val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getTrending().enqueue(object : Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if(response.isSuccessful) {
Log.d("Response",response.body().toString())
val popularDTO = response.body()
popularDTO?.results?.forEach {
val movie = Mapper.MapToMovie(it)
listMovie.add(movie)
movie.title?.let { it1 -> Log.d("Movie", it1) }
}
}
callback(listMovie)
}
override fun onFailure(call: Call<PopularDTO>, t: Throwable) {
Log.d("Error failure", t.message.toString())
}
})
}
}

@ -1,16 +0,0 @@
package fr.iut.pm.movieapplication.repository
import android.util.Log
import fr.iut.pm.movieapplication.api.RetrofitInstance
import fr.iut.pm.movieapplication.api.dtos.PopularDTO
import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class PopularRepository() {
suspend fun getPopular(): PopularDTO? {
throw NoSuchMethodError()
}
}

@ -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<MediaResult> = withContext(Dispatchers.IO) {
val listMediaResult : MutableList<MediaResult> = 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<MediaResult> = withContext(Dispatchers.IO) {
val listMediaResult : MutableList<MediaResult> = 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<MediaResult> = withContext(Dispatchers.IO) {
val listMediaResult : MutableList<MediaResult> = 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<MediaResult> = withContext(Dispatchers.IO) {
val listMediaResult : MutableList<MediaResult> = 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
}
}

@ -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<Movie> = withContext(Dispatchers.IO)
{
val listMovie : MutableList<Movie> = 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<Movie> = withContext(Dispatchers.IO)
{
val listMovie : MutableList<Movie> = 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<Movie> = withContext(Dispatchers.IO)
{
val listMovie : MutableList<Movie> = 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<Movie> = withContext(Dispatchers.IO)
{
val listMovie : MutableList<Movie> = 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
}
}

@ -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<TvShow> {
val listMovie : MutableList<TvShow> = 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<TvShow> {
val listMovie : MutableList<TvShow> = 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<TvShow> {
val listMovie : MutableList<TvShow> = 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<TvShow> {
val listMovie : MutableList<TvShow> = 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
}
}

@ -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<Movie> = withContext(Dispatchers.IO) {
val listMovie : MutableList<Movie> = mutableListOf()
val listMoviesEntities = movieDAO.getAllMovies()
listMoviesEntities.forEach { movieEntity ->
listMovie.add(MovieLocalMapper.mapToMovie(movieEntity))
}
listMovie
}
}

@ -1,72 +1,47 @@
package fr.iut.pm.movieapplication.ui.activity
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.widget.SearchView.OnQueryTextListener
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.api.RetrofitInstance
import fr.iut.pm.movieapplication.api.config.GlobalImageConfig
import fr.iut.pm.movieapplication.repository.MovieRepository
import fr.iut.pm.movieapplication.ui.fragments.HomeSectionsFragment
import fr.iut.pm.movieapplication.ui.fragments.MoviesFragment
import fr.iut.pm.movieapplication.ui.fragments.ShowsFragment
import fr.iut.pm.movieapplication.utils.Constants
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.awaitResponse
import fr.iut.pm.movieapplication.ui.fragments.*
class MainActivity : AppCompatActivity() {
val movieRepository : MovieRepository = MovieRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragments(HomeSectionsFragment(this))
loadFragments(HomeSectionsFragment())
val navigationView = findViewById<BottomNavigationView>(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(ShowsFragment(this))
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 {
@ -78,13 +53,17 @@ class MainActivity : AppCompatActivity() {
searchView.setOnQueryTextListener( object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
TODO("Not yet implemented")
return false
if(!query.isNullOrEmpty()) {
loadFragments(SearchResultFragment.newInstance(query))
}
return true;
}
override fun onQueryTextChange(newText: String?): Boolean {
TODO("Not yet implemented")
return false
//A améliorer
override fun onQueryTextChange(query: String?): Boolean {
if(!query.isNullOrEmpty()) {
loadFragments(SearchResultFragment.newInstance(query))
}
return true
}
})

@ -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
}
}

@ -1,52 +0,0 @@
package fr.iut.pm.movieapplication.ui.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.net.toUri
import androidx.recyclerview.widget.RecyclerView
import coil.load
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.api.config.GlobalImageConfig
import fr.iut.pm.movieapplication.model.Movie
import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.utils.Constants.Companion.IMG_URL
class HomeItemAdapter(
private val context: MainActivity,
private val layoutId: Int,
private val list: List<Movie>
) : RecyclerView.Adapter<HomeItemAdapter.ViewHolder>() {
class ViewHolder(view : View) : RecyclerView.ViewHolder(view) {
val itemImage: ImageView = view.findViewById<ImageView>(R.id.item_image)
val itemName: TextView = view.findViewById<TextView>(R.id.item_name)
val itemDate: TextView = view.findViewById<TextView>(R.id.item_date)
}
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) {
val currentItem = list[position]
Log.d("SINGLETON", GlobalImageConfig.baseUrl)
val imgUri = currentItem.posterPath?.let {
(IMG_URL + it).toUri().buildUpon().scheme("https").build()
}
Log.d("SINGLETON", imgUri.toString() )
holder.itemImage.load(imgUri)
holder.itemName.text = currentItem.title
holder.itemDate.text = currentItem.releaseDate
}
override fun getItemCount(): Int = list.size
}

@ -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<MediaResult, MediaAdapter.ViewHolder>(DiffUtilMediaCallback) {
private object DiffUtilMediaCallback : DiffUtil.ItemCallback<MediaResult>() {
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))
}

@ -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<Movie, MovieAdapter.ViewHolder>(DiffUtilMovieCallback) {
private object DiffUtilMovieCallback : DiffUtil.ItemCallback<Movie>() {
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))
}

@ -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<MediaResult, SearchResultAdapter.ViewHolder>(DiffUtilMediaCallback) {
private object DiffUtilMediaCallback : DiffUtil.ItemCallback<MediaResult>() {
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))
}

@ -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<TvShow, TvShowAdapter.ViewHolder>(DiffUtilTvShowCallback) {
private object DiffUtilTvShowCallback : DiffUtil.ItemCallback<TvShow>() {
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))
}

@ -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<MoviesDialogVM> { 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)
}
}

@ -0,0 +1,57 @@
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.dialog.MovieDialog
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<FavoritesVM>()
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) {
val dialog = MovieDialog()
val args = Bundle()
args.putInt("movieId",movieId)
dialog.arguments = args
dialog.show(parentFragmentManager, "tag")
}
}

@ -1,45 +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 fr.iut.pm.movieapplication.api.dtos.MovieResultDTO
import fr.iut.pm.movieapplication.model.Movie
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 {
private lateinit var homeSectionsViewModel : HomeSectionsVM
private val homeSectionsVM by viewModels<HomeSectionsVM>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_home_sections, container, false)
private val trendsAdapter = MediaAdapter(this)
private val popularMoviesAdapter = MediaAdapter(this)
private val popularTvShowsAdapter = MediaAdapter(this)
//get the trends RecyclerView
context.movieRepository.getTrends {
val homeTrendsRecyclerView = view?.findViewById<RecyclerView>(R.id.home_trends_recycler_view)
homeTrendsRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page,it)
homeTrendsRecyclerView?.addItemDecoration(HomeItemDecoration())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?)
: 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)
}
//get the popularity RecyclerView
context.movieRepository.getPopularMovies {
val homePopularityRecyclerView = view?.findViewById<RecyclerView>(R.id.home_popularity_recycler_view)
homePopularityRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page,it)
homePopularityRecyclerView?.addItemDecoration(HomeItemDecoration())
homeSectionsVM.getPopularTvShowsLiveData().observe(viewLifecycleOwner) {
popularTvShowsAdapter.submitList(it)
}
//get the free RecyclerView
val homeFreeRecyclerView = view?.findViewById<RecyclerView>(R.id.home_free_recycler_view)
homeFreeRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page,ArrayList())
homeFreeRecyclerView?.addItemDecoration(HomeItemDecoration())
return view
}
override fun onMediaSelected(mediaId: Int) {
Log.d("ITEM SELECTED", mediaId.toString())
}
}

@ -4,39 +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.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.api.RetrofitInstance
import fr.iut.pm.movieapplication.model.Movie
import fr.iut.pm.movieapplication.repository.MovieRepository
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.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
import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVMFactory
import fr.iut.pm.movieapplication.ui.viewmodel.viewModelFactory
import kotlinx.coroutines.launch
class MoviesFragment(
private val context : MainActivity
) : Fragment() {
private val moviesVM: MoviesVM by viewModels{ MoviesVMFactory(repository = MovieRepository())}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_movies, container, false)
context.movieRepository.getPopularMovies { listMovies ->
val moviesRecyclerView = view?.findViewById<RecyclerView>(R.id.movies_item_recycler_view)
moviesRecyclerView?.adapter = HomeItemAdapter(context, R.layout.item_vertical_fragment, listMovies)
moviesRecyclerView ?. layoutManager = GridLayoutManager (context, 2)
moviesRecyclerView?.addItemDecoration(CategoryItemDecoration())
) : Fragment(), MovieSelection {
private val moviesVM by viewModels<MoviesVM>()
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
with(binding.moviesItemRecyclerView) {
adapter = moviesAdapter
}
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())
}
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)
}
}
return view
override fun onMovieSelected(movieId: Int) {
val dialog = MovieDialog()
val args = Bundle()
args.putInt("movieId",movieId)
dialog.arguments = args
dialog.show(parentFragmentManager, "tag")
}
}
}

@ -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<SearchResultVM> { 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
}
}
}

@ -1,15 +0,0 @@
package fr.iut.pm.movieapplication.ui.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import fr.iut.pm.movieapplication.ui.activity.MainActivity
class ShowsFragment(
private val context : MainActivity
) : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}

@ -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<TvShowVM>()
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)
}
}
}

@ -0,0 +1,6 @@
package fr.iut.pm.movieapplication.ui.interfaces
interface MediaSelection {
fun onMediaSelected(mediaId : Int)
}

@ -0,0 +1,6 @@
package fr.iut.pm.movieapplication.ui.interfaces
interface MovieSelection {
fun onMovieSelected(movieId : Int)
}

@ -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<List<Movie>> = MutableLiveData()
fun getFavoritesLiveData() : MutableLiveData<List<Movie>> = _favoritesLiveData
init {
//with dispatchers.IO
viewModelScope.launch{
_favoritesLiveData.value = repository.getAllMovies()
}
}
}

@ -1,4 +1,33 @@
package fr.iut.pm.movieapplication.ui.viewmodel
class HomeSectionsVM {
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<List<MediaResult>> = MutableLiveData()
fun getTrendsLiveData() : MutableLiveData<List<MediaResult>> = _trendsLiveData
private var _popularMoviesLiveData : MutableLiveData<List<MediaResult>> = MutableLiveData()
fun getPopularMoviesLiveData() : MutableLiveData<List<MediaResult>> = _popularMoviesLiveData
private var _popularTvShowsLiveData : MutableLiveData<List<MediaResult>> = MutableLiveData()
fun getPopularTvShowsLiveData() : MutableLiveData<List<MediaResult>> = _popularTvShowsLiveData
init {
viewModelScope.launch {
_trendsLiveData.postValue(repository.getTrends())
_popularMoviesLiveData.postValue(repository.getPopularMovies())
_popularTvShowsLiveData.postValue(repository.getPopularTvShows())
}
}
}

@ -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<MovieDetails> = MutableLiveData()
fun getMovieDetailLiveData() : LiveData<MovieDetails> = _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 fun<T:ViewModel>create(modelClass:Class<T>) : T
{
return MoviesDialogVM(movieId) as T
}
}

@ -1,37 +1,109 @@
package fr.iut.pm.movieapplication.ui.viewmodel
import androidx.lifecycle.*
import fr.iut.pm.movieapplication.api.dtos.MovieResultDTO
import fr.iut.pm.movieapplication.model.Movie
import fr.iut.pm.movieapplication.repository.MovieRepository
import kotlinx.coroutines.CoroutineScope
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(private val repository: MovieRepository) : ViewModel() {
class MoviesVM() : ViewModel() {
/**
* The movie repository used to get our data
*/
private val repository = MovieAPIRepository()
private val _popularMovies = MutableLiveData<List<Movie>>()
val popularMovies : LiveData<List<Movie>> = _popularMovies
/**
* The MutableLiveData
*/
private var _moviesLiveData : MutableLiveData<List<Movie>> = MutableLiveData<List<Movie>>()
init {
//loadData()
/**
* Getter of the LiveData
*/
fun getMoviesLiveData() : LiveData<List<Movie>> = _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)
}
}
}
suspend fun loadData() {
viewModelScope.launch {
repository.getPopularMovies { movies ->
_popularMovies.value = movies
/**
* 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)
}
}.join()
"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<Movie>
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)
}
}
class MoviesVMFactory(
private val repository: MovieRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MoviesVM(repository) as T
}
}

@ -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<String> = MutableLiveData()
fun getQueryLiveData() : LiveData<String> = _queryLiveData
private var _searchResultLiveData : MutableLiveData<List<MediaResult>> = MutableLiveData()
fun getSearchResultLiveData() : LiveData<List<MediaResult>> = _searchResultLiveData
init {
_queryLiveData.value = query
viewModelScope.launch {
_searchResultLiveData.postValue(repository.search(query))
}
}
}
class SearchResultVMFactory(private var query : String) : ViewModelProvider.Factory
{
override fun<T:ViewModel>create(modelClass:Class<T>) : T
{
return SearchResultVM(query) as T
}
}

@ -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<List<TvShow>> = MutableLiveData<List<TvShow>>()
fun getTvShowLiveData() : LiveData<List<TvShow>> = 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())
}
}
}
}

@ -1,9 +0,0 @@
package fr.iut.pm.movieapplication.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) =
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = f() as T
}

@ -1,10 +1,19 @@
package fr.iut.pm.movieapplication.utils
import fr.iut.pm.movieapplication.BuildConfig
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"
const val API_KEY = BuildConfig.API_KEY
//VIEW PAGINATION
const val PAGE_SIZE = 20
const val MAX_PAGE = 1000
}
}

@ -1,36 +0,0 @@
package fr.iut.pm.movieapplication.utils
import fr.iut.pm.movieapplication.api.dtos.MovieResultDTO
import fr.iut.pm.movieapplication.model.Movie
object Mapper {
fun MapToMovie( movieDTO : MovieResultDTO) : Movie {
return Movie(
adult = movieDTO.adult,
posterPath = movieDTO.posterPath,
overview = movieDTO.overview,
releaseDate = movieDTO.releaseDate,
movieId = movieDTO.id,
originalTitle = movieDTO.originalTitle,
originalLanguage = movieDTO.originalLanguage,
title = movieDTO.title,
backdropPath = movieDTO.backdropPath,
popularity = movieDTO.popularity,
voteCount = movieDTO.voteCount,
voteAverage = movieDTO.voteAverage,
budget = null,
genres = null,
homePage = null,
productionCompanies = null,
productionCountries = null,
revenue = null,
runtime = null,
status = null,
tagLine = null
)
}
}

@ -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
)
}
}

@ -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
)
}
}

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="favoritesVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.FavoritesVM" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/movies_item_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/default_margin"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:listitem="@layout/item_movie_category"
/>
</LinearLayout>
</layout>

@ -1,69 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/section_nested_scroll_view">
<data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<import type="android.view.View" />
<TextView
android:id="@+id/home_page_trends_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_trends_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_trends_recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<variable
name="homeSectionsVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM" />
</data>
<TextView
android:id="@+id/home_page_popularity_section_title"
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_popularity_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_popularity_recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
android:contentDescription="@string/section_nested_scroll_view">
<TextView
android:id="@+id/home_page_free_section_title"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_free_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_free_recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</LinearLayout>
>
<TextView
android:id="@+id/home_page_trends_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_trends_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_trends_recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_search_results" />
<TextView
android:id="@+id/home_page_popularity_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_popular_movies_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_popular_movies_recycler_view"
android:layout_width="match_parent"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_search_results" />
<TextView
android:id="@+id/home_page_free_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_popular_tv_shows_section_title"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
style="@style/SectionTitleStyle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_popular_tv_shows_recycler_view"
android:layout_width="match_parent"
android:layout_marginBottom="@dimen/default_margin"
android:layout_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.core.widget.NestedScrollView>
</layout>

@ -1,14 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/movies_item_recycler_view"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="moviesVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.MoviesVM" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/default_margin"
/>
android:layout_height="match_parent"
android:orientation="vertical">
<Spinner
android:id="@+id/category_movie_spinner"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin" />
<ProgressBar
android:id="@+id/movie_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/movies_item_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/default_margin"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
app:onScrollListener = "@{moviesVM.scrollListener}"
tools:listitem="@layout/item_movie_category"
/>
</LinearLayout>
</LinearLayout>
</layout>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="searchResultsVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.SearchResultVM" />
</data>
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_results_item_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/default_margin"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:listitem="@layout/item_search_results" />
</LinearLayout>
</layout>

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="tvShowVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.TvShowVM" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Spinner
android:id="@+id/category_tv_show_spinner"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tv_shows_item_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/default_margin"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
android:adapter="@{tvShowVM.tvShowAdapter}"
tools:listitem="@layout/item_tv_show_category"
/>
</LinearLayout>
</layout>

@ -1,45 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_marginTop="10dp">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:cardCornerRadius="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="mediaResult"
type="fr.iut.pm.movieapplication.model.media.MediaResult" />
</data>
<LinearLayout
android:layout_width="150dp"
android:layout_height="match_parent"
android:orientation="vertical" >
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="150dp"
android:layout_height="match_parent"
android:background="@color/black"
android:scaleType="centerCrop" />
app:cardCornerRadius="5dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="210dp"
android:background="@color/black"
android:scaleType="centerCrop"
android:contentDescription="description" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/item_name" />
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{mediaResult.title}"/>
<TextView
android:id="@+id/item_date"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{mediaResult.releaseDate}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</layout>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="movie"
type="fr.iut.pm.movieapplication.model.media.movie.Movie" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@color/black"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{movie.title.length() > 20 ? movie.title.substring(0,20) + `...` : movie.title}"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="mediaResult"
type="fr.iut.pm.movieapplication.model.media.MediaResult" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@color/black"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{mediaResult.title}"/>
<TextView
android:id="@+id/item_date"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{mediaResult.releaseDate}"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</layout>

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<data>
<variable
name="tvShow"
type="fr.iut.pm.movieapplication.model.media.tvshow.TvShow" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@color/black"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{tvShow.name.length() > 20 ? tvShow.name.substring(0,20) + `...` : tvShow.name }"/>
<TextView
android:id="@+id/item_date"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
android:text="@{tvShow.firstAirDate}"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="175dp"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="@dimen/default_margin"
app:layout_constraintTop_toTopOf="parent"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="210dp"
android:scaleType="centerCrop"
android:background="@color/black" />
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
/>
<TextView
android:id="@+id/item_date"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center"
android:textAlignment="center"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="movieDetails"
type="fr.iut.pm.movieapplication.model.media.movie.MovieDetails" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/default_margin">
<ImageView
android:id="@+id/close_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:src="@drawable/ic_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="Close" />
<ImageView
android:id="@+id/details_image"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_marginTop="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:autoText="true"
android:hint="The title of the movie"
android:text="@{movieDetails.title}"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000"
app:layout_constraintEnd_toStartOf="@+id/close_item"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/overview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@{movieDetails.overview.length() > 150 ? movieDetails.overview.substring(0, 150) + `...` : movieDetails.overview}"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/register_movie_details_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/details_image"
app:layout_constraintTop_toBottomOf="@+id/details_image" />
<Button
android:id="@+id/register_movie_details_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Enregistrer"
android:gravity="center_horizontal|center_vertical"
app:layout_constraintBottom_toTopOf="@+id/button2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Voir détails"
android:gravity="center_horizontal|center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/button"
app:layout_constraintStart_toStartOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

@ -14,7 +14,7 @@
android:icon="@drawable/ic_tv_shows"
android:title="@string/bottom_series_item" />
<item
android:id="@+id/artist_page"
android:id="@+id/favorites_page"
android:icon="@drawable/ic_baseline_star_border_24"
android:title="@string/bottom_artist_item" />
android:title="@string/bottom_favorite_item" />
</menu>

@ -14,7 +14,7 @@
android:icon="@drawable/ic_tv_shows"
android:title="@string/bottom_series_item" />
<item
android:id="@+id/artist_page"
android:id="@+id/favorites_page"
android:icon="@drawable/ic_baseline_star_border_24"
android:title="@string/bottom_artist_item" />
android:title="@string/bottom_favorite_item" />
</menu>

@ -13,4 +13,28 @@
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="DefaultTextStyle">
<item name="android:textColor">@color/white</item>
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@font/source_sans_pro</item>
</style>
<style name="SubtitleTextStyle" parent="DefaultTextStyle">
<item name="android:textColor">@color/white</item>
<item name="android:textSize">20sp</item>
</style>
<style name="SectionTitleStyle" parent="DefaultTextStyle">
<item name="android:textSize">24sp</item>
</style>
<style name="TitleTextStyle" parent="SubtitleTextStyle">
<item name="android:textColor">@color/white</item>
<item name="android:textSize">24sp</item>
</style>
<style name="BottomMenuStyle" parent="Widget.Material3.BottomNavigationView">
<item name="android:textSize">16sp</item>
</style>
</resources>

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

@ -4,14 +4,31 @@
<string name="bottom_home_item">Accueil</string>
<string name="bottom_movies_item">Films</string>
<string name="bottom_series_item">Séries</string>
<string name="bottom_artist_item">Artistes</string>
<string name="bottom_favorite_item">Favoris</string>
<!-- Home page -->
<string name="home_page_title">Bienvenue,</string>
<string name="home_page_subtitle">Des millions de films, émissions télévisées et artistes…</string>
<string name="home_page_trends_section_title">Tendances</string>
<string name="home_page_popularity_section_title">Populaires</string>
<string name="home_page_free_section_title">Gratuits</string>
<string name="home_page_popular_movies_section_title">Films populaires</string>
<string name="home_page_popular_tv_shows_section_title">Séries populaires</string>
<string name="section_nested_scroll_view">section_nested_scroll_view</string>
<!-- Filter name for movie category -->
<!-- Category page -->
<string-array name="movie_filter">
<item>Populaires</item>
<item>Du moment</item>
<item>À venir</item>
<item>Les mieux notés</item>
</string-array>
<string-array name="tv_show_filter">
<item>Populaires</item>
<item>Diffusées aujourd\'hui</item>
<item>En cours de diffusion</item>
<item>Les mieux notées</item>
</string-array>
</resources>

@ -7,7 +7,7 @@ plugins {
ext {
activityVersion = '1.6.1'
appCompatVersion = '1.6.0'
appCompatVersion = '1.6.1'
constraintLayoutVersion = '2.1.4'
coreTestingVersion = '2.1.0'
coroutines = '1.6.4'

Loading…
Cancel
Save