🔀merge branch 'develop' -> 'master'

master
Jordan ARTZET 2 years ago
commit a992dcd64f

@ -36,31 +36,48 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion"
implementation "androidx.fragment:fragment-ktx:1.5.5"
implementation "androidx.activity:activity-ktx:$rootProject.activityVersion"
implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion"
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$rootProject.lifecycleVersion"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
//Coil
implementation "io.coil-kt:coil:1.1.1"
// UI
implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion"
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Moshi
implementation "com.squareup.moshi:moshi-kotlin:1.13.0"
// Retrofit & Moshi
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
// Testing
testImplementation "junit:junit:$rootProject.junitVersion"

@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<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"
@ -11,7 +14,7 @@
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:exported="true">

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

@ -0,0 +1,57 @@
package fr.iut.pm.movieapplication.api
import fr.iut.pm.movieapplication.api.dtos.MovieDetailsDTO
import fr.iut.pm.movieapplication.api.dtos.ResultDTO
import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface MovieApplicationAPI {
// Movie
@GET("movie/popular")
suspend fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY, @Query("language") language : String = "fr", @Query("page") page : Int = 1) : Response<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}")
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("tv/{tv_id}")
fun getShowDetails(@Path("tv_id") tvId : Int, @Query("api_key") apiKey: String = API_KEY)
}

@ -0,0 +1,23 @@
package fr.iut.pm.movieapplication.api
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import fr.iut.pm.movieapplication.utils.Constants.Companion.BASE_URL
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
object RetrofitInstance {
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val api: MovieApplicationAPI = retrofit.create(MovieApplicationAPI::class.java)
}

@ -0,0 +1,51 @@
package fr.iut.pm.movieapplication.api.config
import android.util.Log
import com.squareup.moshi.Json
object GlobalImageConfig {
@Json(name = "base_url")
private var _baseUrl : String = ""
val baseUrl : String = _baseUrl
@Json(name = "secure_base_url")
private var _secureBaseUrl : String = ""
val secureBaseUrl = _secureBaseUrl
@Json(name = "backdrop_sizes")
private var backdropSizes : List<String> = listOf()
@Json(name = "logo_sizes")
private var logoSizes : List<String> = listOf()
@Json(name = "poster_sizes")
private var posterSizes : List<String> = listOf()
@Json(name = "profile_sizes")
private var profilSizes : List<String> = listOf()
@Json(name = "still_sizes")
private var stillSizes : List<String> = listOf()
fun updateConfig(config: ImageConfig) {
Log.d("BASE URL IMAGE", baseUrl)
_baseUrl = config.baseUrl
_secureBaseUrl = config.secureBaseUrl
posterSizes = config.posterSizes
backdropSizes = config.backdropSizes
}
}
data class ImageConfig(
@Json(name = "images.base_url")
val baseUrl : String,
@Json(name = "images.secure_base_url")
val secureBaseUrl : String,
@Json(name = "images.backdrop_sizes")
val backdropSizes : List<String>,
@Json(name = "images.logo_sizes")
val logoSizes : List<String>,
@Json(name = "images.poster_sizes")
val posterSizes : List<String>,
@Json(name = "images.profile_sizes")
val profileSizes : List<String>,
@Json(name = "images.still_sizes")
val stillSizes : List<String>
)

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

@ -0,0 +1,29 @@
package fr.iut.pm.movieapplication.api.dtos
import com.squareup.moshi.Json
open class MovieDTO (
@Json(name = "poster_path")
open val posterPath: String?,
open val adult: Boolean,
open val overview: String?,
@Json(name = "release_date")
open val releaseDate: String,
open val id: Int,
@Json(name = "original_title")
open val originalTitle: String,
@Json(name = "original_language")
open val originalLanguage: String,
open val title: String,
@Json(name = "backdrop_path")
open val backdropPath: String?,
open val popularity: Double,
@Json(name = "vote_count")
open val voteCount: Int,
@Json(name = "vote_average")
open val voteAverage: Double
){
}

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

@ -0,0 +1,39 @@
package fr.iut.pm.movieapplication.api.dtos
import com.squareup.moshi.Json
class MovieResultDTO(
@Json(name = "poster_path")
val posterPath : String?,
@Json(name = "adult")
val adult : Boolean,
@Json(name = "overview")
val overview : String,
@Json(name = "release_date")
val releaseDate : String?,
@Json(name = "genre_ids")
val genreIds : List<Int>?,
@Json(name = "id")
val id : Int,
@Json(name = "original_title")
val originalTitle : String?,
@Json(name = "original_language")
val originalLanguage : String?,
@Json(name = "title")
val title : String?,
@Json(name = "backdrop_path")
val backdropPath : String?,
@Json(name = "popularity")
val popularity : Double?,
@Json(name = "vote_count")
val voteCount : Int?,
@Json(name = "vote_average")
val voteAverage : Double?
) {
}

@ -0,0 +1,18 @@
package fr.iut.pm.movieapplication.api.dtos
import com.squareup.moshi.Json
class ResultDTO(
@Json(name = "page")
val page : Int,
@Json(name = "results")
val results : List<MediaResultDTO>,
@Json(name = "total_results")
val totalResults : Int,
@Json(name = "total_pages")
val totalPages : Int
) {}

@ -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,15 +1,61 @@
package fr.iut.pm.movieapplication.data.dao
import androidx.room.Dao
import androidx.room.Query
import fr.iut.pm.movieapplication.model.Movie
import androidx.room.*
import fr.iut.pm.movieapplication.data.entities.*
import fr.iut.pm.movieapplication.model.media.movie.MovieDetails
import kotlinx.coroutines.flow.Flow
@Dao
interface MovieDAO {
@Query("SELECT * FROM movies_table ORDER BY original_title ASC")
fun getMovieByAlphabetizeMovie() : Flow<List<Movie>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(movieEntity : MovieEntity)
/**
* Get all movies from the movies_table
*/
@Query("SELECT * FROM movies_table")
suspend fun getAllMovies() : List<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)
}
@Transaction
@Query("SELECT * FROM movies_details_table WHERE movie_id = :movieId")
suspend fun getMovieDetailsWithGenresById(movieId: Int): MovieDetailsWithGenres
}

@ -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,71 +0,0 @@
package fr.iut.pm.movieapplication.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.Relation
import java.util.Date
@Entity(tableName = "movies_table")
data class Movie(
@ColumnInfo(name = "adult")
val adult: Boolean,
@ColumnInfo(name = "budget")
val budget: Int,
@ColumnInfo(name = "genres")
val genres: Array<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: Int,
@ColumnInfo(name = "poster_path")
val posterPath : String?,
@Embedded
val productionCompanies: Array<ProductionCompany>,
@Relation(
parentColumn = "movieId",
entityColumn = ""
)
@ColumnInfo(name = "production_countries")
val productionCountries: Array<ProductionCountry>,
@ColumnInfo(name = "release_date")
val releaseDate: Date,
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: Number,
@ColumnInfo(name = "vote_count")
val voteCount : Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Movie
if (movieId != other.movieId) return false
return true
}
override fun hashCode(): Int {
return movieId
}
}

@ -0,0 +1,18 @@
package fr.iut.pm.movieapplication.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import fr.iut.pm.movieapplication.model.media.movie.MovieDetails
data class Popular(
@ColumnInfo("page")
val page : Int,
@ColumnInfo("results")
@Embedded
val results : List<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 {
}

@ -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,57 +1,74 @@
package fr.iut.pm.movieapplication.ui.activity
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.Menu
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.ui.fragments.HomeSectionsFragment
import fr.iut.pm.movieapplication.ui.fragments.MoviesFragment
import fr.iut.pm.movieapplication.ui.fragments.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragments(HomeSectionsFragment(this))
loadFragments(HomeSectionsFragment())
val navigationView = findViewById<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(TvShowsFragment())
return@setOnItemSelectedListener true
}
R.id.favorites_page -> {
loadFragments(FavoritesFragment())
return@setOnItemSelectedListener true
}
else -> false
}
}
/*
//Trends fragment injected in main activity
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, HomeSectionsFragment())
transaction.addToBackStack(null)
transaction.commit()
*/
if(Build.VERSION.SDK_INT < 33) {
// Hide the status bar.
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
// Remember that you should never show the action bar if the
// status bar is hidden, so hide that too if necessary.
actionBar?.hide()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.app_menu, menu)
val searchItem = menu?.findItem(R.id.search_bar)
val searchView : SearchView = searchItem?.actionView as SearchView
searchView.setOnQueryTextListener( object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
if(!query.isNullOrEmpty()) {
loadFragments(SearchResultFragment.newInstance(query))
}
return true;
}
//A améliorer
override fun onQueryTextChange(query: String?): Boolean {
if(!query.isNullOrEmpty()) {
loadFragments(SearchResultFragment.newInstance(query))
}
return true
}
})
return true
}
private fun loadFragments(fragment: Fragment) {

@ -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,29 +0,0 @@
package fr.iut.pm.movieapplication.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.ui.activity.MainActivity
class HomeItemAdapter(
private val context : MainActivity,
private val layoutId : Int
) : RecyclerView.Adapter<HomeItemAdapter.ViewHolder>() {
class ViewHolder(view : View) : RecyclerView.ViewHolder(view) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater
.from(parent.context)
.inflate(layoutId, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
override fun getItemCount(): Int = 5
}

@ -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,52 @@
package fr.iut.pm.movieapplication.ui.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import fr.iut.pm.movieapplication.databinding.FragmentFavoritesCategoryBinding
import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter
import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection
import fr.iut.pm.movieapplication.ui.viewmodel.FavoritesVM
class FavoritesFragment : Fragment(), MovieSelection {
private val favoritesVM by viewModels<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) {
TODO("Not yet implemented")
}
}

@ -1,37 +1,73 @@
package fr.iut.pm.movieapplication.ui.fragments
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import fr.iut.pm.movieapplication.R
import androidx.fragment.app.viewModels
import fr.iut.pm.movieapplication.databinding.FragmentHomeSectionsBinding
import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.ui.adapter.HomeItemAdapter
import fr.iut.pm.movieapplication.ui.adapter.MediaAdapter
import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration
import fr.iut.pm.movieapplication.ui.interfaces.MediaSelection
import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection
import fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM
class HomeSectionsFragment(
private val context : MainActivity
) : Fragment() {
class HomeSectionsFragment() : Fragment(), MediaSelection {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_home_sections, container, false)
private val homeSectionsVM by viewModels<HomeSectionsVM>()
//get the trends RecyclerView
val homeTrendsRecyclerView = view?.findViewById<RecyclerView>(R.id.home_trends_recycler_view)
homeTrendsRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page)
homeTrendsRecyclerView?.addItemDecoration(HomeItemDecoration())
private val trendsAdapter = MediaAdapter(this)
private val popularMoviesAdapter = MediaAdapter(this)
private val popularTvShowsAdapter = MediaAdapter(this)
//get the popularity RecyclerView
val homePopularityRecyclerView = view?.findViewById<RecyclerView>(R.id.home_popularity_recycler_view)
homePopularityRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page)
homePopularityRecyclerView?.addItemDecoration(HomeItemDecoration())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?)
: View? {
//get the free RecyclerView
val homeFreeRecyclerView = view?.findViewById<RecyclerView>(R.id.home_free_recycler_view)
homeFreeRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_horizontal_home_page)
homeFreeRecyclerView?.addItemDecoration(HomeItemDecoration())
return view
val binding = FragmentHomeSectionsBinding.inflate(inflater)
binding.homeSectionsVM = homeSectionsVM
binding.lifecycleOwner = viewLifecycleOwner
with(binding.homeTrendsRecyclerView) {
adapter = trendsAdapter
addItemDecoration(HomeItemDecoration())
}
with(binding.homePopularMoviesRecyclerView) {
adapter = popularMoviesAdapter
addItemDecoration(HomeItemDecoration())
}
with(binding.homePopularTvShowsRecyclerView) {
adapter = popularTvShowsAdapter
addItemDecoration(HomeItemDecoration())
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeSectionsVM.getTrendsLiveData().observe(viewLifecycleOwner) {
trendsAdapter.submitList(it)
}
homeSectionsVM.getPopularMoviesLiveData().observe(viewLifecycleOwner) {
popularMoviesAdapter.submitList(it)
}
homeSectionsVM.getPopularTvShowsLiveData().observe(viewLifecycleOwner) {
popularTvShowsAdapter.submitList(it)
}
}
override fun onMediaSelected(mediaId: Int) {
Log.d("ITEM SELECTED", mediaId.toString())
}
}

@ -4,30 +4,76 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.fragment.app.viewModels
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.ui.adapter.CategoryItemDecoration
import fr.iut.pm.movieapplication.ui.adapter.HomeItemAdapter
import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration
import fr.iut.pm.movieapplication.databinding.FragmentMoviesBinding
import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter
import fr.iut.pm.movieapplication.ui.dialog.MovieDialog
import fr.iut.pm.movieapplication.ui.interfaces.MovieSelection
import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVM
class MoviesFragment(
private val context : MainActivity
) : Fragment() {
) : Fragment(), MovieSelection {
private val moviesVM by viewModels<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
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_movies, container, false)
with(binding.moviesItemRecyclerView) {
adapter = moviesAdapter
}
//get the recycler view
val moviesRecyclerView = view?.findViewById<RecyclerView>(R.id.movies_item_recycler_view)
moviesRecyclerView?.adapter = HomeItemAdapter(context,R.layout.item_vertical_fragment)
moviesRecyclerView?.layoutManager = GridLayoutManager(context, 2)
moviesRecyclerView?.addItemDecoration(CategoryItemDecoration())
val adapter = ArrayAdapter.createFromResource(
requireContext(),
R.array.movie_filter,
android.R.layout.simple_spinner_item
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
with(binding.categoryMovieSpinner)
{
this.adapter = adapter
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
moviesVM.getData(selectedItem.toString())
}
return view
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
moviesVM.getMoviesLiveData().observe(viewLifecycleOwner) {
moviesAdapter.submitList(it)
}
}
override fun onMovieSelected(movieId: Int) {
val dialog = MovieDialog()
val args = Bundle()
args.putInt("movieId",movieId)
dialog.arguments = args
dialog.show(parentFragmentManager, "tag")
}
}
}

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

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

@ -0,0 +1,33 @@
package fr.iut.pm.movieapplication.ui.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import fr.iut.pm.movieapplication.model.media.MediaResult
import fr.iut.pm.movieapplication.repository.api.MediaRepository
import kotlinx.coroutines.launch
class HomeSectionsVM : ViewModel() {
private val repository = MediaRepository()
private var _trendsLiveData : MutableLiveData<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
}
}

@ -0,0 +1,109 @@
package fr.iut.pm.movieapplication.ui.viewmodel
import androidx.lifecycle.*
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.iut.pm.movieapplication.model.media.movie.Movie
import fr.iut.pm.movieapplication.repository.api.MovieAPIRepository
import fr.iut.pm.movieapplication.utils.Constants.Companion.MAX_PAGE
import kotlinx.coroutines.launch
class MoviesVM() : ViewModel() {
/**
* The movie repository used to get our data
*/
private val repository = MovieAPIRepository()
/**
* The MutableLiveData
*/
private var _moviesLiveData : MutableLiveData<List<Movie>> = MutableLiveData<List<Movie>>()
/**
* 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)
}
}
}
/**
* Currrent page where the data are obtained
*/
private var currentPage = 1
/**
* Get the data with a given filter
* @param filter filter applied to get data
*/
fun getData(filter : String) {
currentFilter = filter
currentPage = 1
when(currentFilter) {
"Populaires" -> viewModelScope.launch {
_moviesLiveData.postValue(repository.getPopularMovies())
repository.getMovieDetails(315162)
}
"Du moment" -> viewModelScope.launch {
_moviesLiveData.postValue(repository.getNowPlayingMovies())
}
"À venir" -> viewModelScope.launch {
_moviesLiveData.postValue(repository.getUpcomingMovies())
}
"Les mieux notés" -> viewModelScope.launch {
_moviesLiveData.postValue(repository.getTopRatedMovies())
}
}
}
/**
* Get more data with the actual filter
* @param page page from which the data are obtained
*/
private fun getMoreData(page : Int = 1) {
var movies : List<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)
}
}
}
}

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

@ -0,0 +1,17 @@
package fr.iut.pm.movieapplication.utils
class Constants {
companion object {
//API
const val BASE_URL = "https://api.themoviedb.org/3/"
const val IMG_URL = "https://image.tmdb.org/t/p/w500"
const val API_KEY = "8f14a279249638d7f247d0d7298b21b4"
//VIEW PAGINATION
const val PAGE_SIZE = 20
const val MAX_PAGE = 1000
}
}

@ -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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@color/light_green"/>
<item android:state_checked="false" android:color="@color/light_grey"/>
</selector>

@ -11,7 +11,7 @@
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="75dp"
android:layout_marginBottom="55dp"
app:layout_constraintBottom_toTopOf="@+id/navigation_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -20,10 +20,14 @@
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_height="wrap_content"
android:background="@color/dark_blue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:itemTextColor="@drawable/item_selector"
app:itemIconTint="@drawable/item_selector"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_navigation_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -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,59 +1,86 @@
<?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">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<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:textSize="20sp" />
<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" />
<TextView
android:id="@+id/home_page_popularity_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_popularity_section_title"
android:textSize="20sp" />
<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" />
<TextView
android:id="@+id/home_page_free_section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/home_page_free_section_title"
android:textSize="20sp" />
<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>
</androidx.core.widget.NestedScrollView>
<data>
<import type="android.view.View" />
<variable
name="homeSectionsVM"
type="fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/section_nested_scroll_view">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<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_height="250dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>

@ -1,13 +1,55 @@
<?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_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:background="@color/light_grey"
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,45 @@
<?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:background="@color/light_grey"
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,46 +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" >
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:cardCornerRadius="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/home_section_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" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/home_section_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nom du film"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
<TextView
android:id="@+id/home_section_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="23 nov 2012"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/home_section_item_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
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="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>
</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,47 +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="match_parent">
<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"
app:layout_constraintTop_toTopOf="parent"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="210dp"
android:background="@color/black" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Nom du film"
android:layout_gravity="center"
android:textAlignment="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="23 nov 2015"
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>

@ -3,21 +3,18 @@
<item
android:id="@+id/home_page"
android:title="@string/bottom_home_item"
android:icon="@drawable/ic_home"
/>
android:title="@string/bottom_home_item" />
<item
android:id="@+id/movies_page"
android:title="@string/bottom_movies_item"
android:icon="@drawable/ic_movie"
/>
android:title="@string/bottom_movies_item" />
<item
android:id="@+id/series_page"
android:title="@string/bottom_series_item"
android:icon="@drawable/ic_tv_shows"
/>
android:title="@string/bottom_series_item" />
<item
android:id="@+id/artist_page"
android:title="@string/bottom_artist_item"
android:icon="@drawable/ic_baseline_star_border_24"/>
android:id="@+id/favorites_page"
android:icon="@drawable/ic_baseline_star_border_24"
android:title="@string/bottom_favorite_item" />
</menu>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/search_bar"
android:title="Search"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="androidx.appcompat.widget.SearchView" />
</menu>

@ -3,21 +3,18 @@
<item
android:id="@+id/home_page"
android:title="@string/bottom_home_item"
android:icon="@drawable/ic_home"
/>
android:title="@string/bottom_home_item" />
<item
android:id="@+id/movies_page"
android:title="@string/bottom_movies_item"
android:icon="@drawable/ic_movie"
/>
android:title="@string/bottom_movies_item" />
<item
android:id="@+id/series_page"
android:title="@string/bottom_series_item"
android:icon="@drawable/ic_tv_shows"
/>
android:title="@string/bottom_series_item" />
<item
android:id="@+id/artist_page"
android:title="@string/bottom_artist_item"
android:icon="@drawable/ic_baseline_star_border_24"/>
android:id="@+id/favorites_page"
android:icon="@drawable/ic_baseline_star_border_24"
android:title="@string/bottom_favorite_item" />
</menu>

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.MovieApplication" parent="Theme.Material3.Dark.NoActionBar">
<style name="Theme.MovieApplication" parent="Theme.Material3.Dark">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/dark_blue</item>
<item name="colorPrimaryVariant">@color/black</item>

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

@ -8,7 +8,9 @@
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="light_grey">#e3e3e3</color>
<color name="dark_blue">#032541</color>
<color name="light_blue">#01B4E4</color>
<color name="lighter_green">#c0fecf</color>
<color name="light_green">#1ed5a9 </color>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="default_margin">20dp</dimen>
</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>

@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.MovieApplication" parent="@style/Theme.Material3.Light">
<style name="Theme.MovieApplication" parent="Theme.Material3.Light">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/light_blue</item>
<item name="colorPrimaryVariant">@color/dark_blue</item>
@ -13,4 +13,30 @@
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<!-- Create the component for the default title -->
<style name="DefaultTextStyle">
<item name="android:textColor">@color/black</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/black</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/black</item>
<item name="android:textSize">24sp</item>
</style>
<style name="BottomMenuStyle" parent="Widget.Material3.BottomNavigationView">
<item name="android:textSize">16sp</item>
</style>
</resources>

@ -7,12 +7,12 @@ 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'
lifecycleVersion = '2.5.1'
materialVersion = '1.7.0'
materialVersion = '1.8.0'
roomVersion = '2.5.0'
kotlin_version = '1.7.20'
// testing

Loading…
Cancel
Save