🚧 add data binding and view model

Data are now binded on the view
features/api/requests/2
Jordan ARTZET 2 years ago
parent fc371bf386
commit e29247f3ce

@ -36,13 +36,17 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
buildFeatures {
dataBinding true
}
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion" implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion"
implementation "androidx.activity:activity-ktx:$rootProject.activityVersion" implementation "androidx.activity:activity-ktx:$rootProject.activityVersion"
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.fragment:fragment-ktx:1.5.5" implementation "androidx.fragment:fragment-ktx:1.5.5"
// Room components // Room components
@ -53,7 +57,7 @@ dependencies {
// Lifecycle components // Lifecycle components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-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 // Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -69,13 +73,11 @@ dependencies {
// Moshi // Moshi
implementation "com.squareup.moshi:moshi-kotlin:1.13.0" implementation "com.squareup.moshi:moshi-kotlin:1.13.0"
//GSON
//implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
// Retrofit // Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit with Scalar Converter // Retrofit with Scalar Converter
//implementation "com.squareup.retrofit2:converter-scalars:2.9.0" implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
// Retrofit with Moshi Converter // Retrofit with Moshi Converter
implementation "com.squareup.retrofit2:converter-moshi:2.9.0" implementation "com.squareup.retrofit2:converter-moshi:2.9.0"

@ -5,6 +5,7 @@ import fr.iut.pm.movieapplication.api.dtos.MovieResultDTO
import fr.iut.pm.movieapplication.api.dtos.PopularDTO import fr.iut.pm.movieapplication.api.dtos.PopularDTO
import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY import fr.iut.pm.movieapplication.utils.Constants.Companion.API_KEY
import retrofit2.Call import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
@ -13,16 +14,16 @@ interface MovieApplicationAPI {
// Movie // Movie
@GET("movie/popular") @GET("movie/popular")
fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY, @Query("page") page : Int = 1) : Call<PopularDTO> suspend fun getPopularMovies(@Query("api_key") apiKey : String = API_KEY, @Query("page") page : Int = 1) : Response<PopularDTO>
@GET("movie/now_playing") @GET("movie/now_playing")
fun getNowPlayingMovies(@Query("api_key") apiKey : String = API_KEY, @Query("page") page : Int = 1) : Call<PopularDTO> suspend fun getNowPlayingMovies(@Query("api_key") apiKey : String = API_KEY, @Query("page") page : Int = 1) : Response<PopularDTO>
@GET("movie/upcoming") @GET("movie/upcoming")
fun getUpcomingMovies(@Query("api_key") apiKey: String = API_KEY, @Query("page") page : Int = 1) : Call<PopularDTO> suspend fun getUpcomingMovies(@Query("api_key") apiKey: String = API_KEY, @Query("page") page : Int = 1) : Response<PopularDTO>
@GET("movie/top_rated") @GET("movie/top_rated")
fun getTopRatedMovies(@Query("api_key") apiKey: String = API_KEY, @Query("page") page : Int = 1) : Call<PopularDTO> suspend fun getTopRatedMovies(@Query("api_key") apiKey: String = API_KEY, @Query("page") page : Int = 1) : Response<PopularDTO>

@ -7,7 +7,7 @@ import com.squareup.moshi.JsonClass
data class MediaResultDTO( data class MediaResultDTO(
@Json(name = "poster_path") @Json(name = "poster_path")
val posterPath : String, val posterPath : String?,
val adult : Boolean, val adult : Boolean,
val overview : String, val overview : String,
@Json(name = "first_air_date") @Json(name = "first_air_date")
@ -25,7 +25,7 @@ data class MediaResultDTO(
val originalLanguage : String, val originalLanguage : String,
val title : String?, val title : String?,
@Json(name = "backdrop_path") @Json(name = "backdrop_path")
val backdropPath : String, val backdropPath : String?,
val popularity : Double, val popularity : Double,
@Json(name = "vote_count") @Json(name = "vote_count")
val voteCount : Int, val voteCount : Int,

@ -5,20 +5,17 @@ data class MediaResult(
val posterPath: String? = null, val posterPath: String? = null,
val adult: Boolean, val adult: Boolean,
val overview: String, val overview: String,
val firstAirDate: String? = null, val releaseDate: String,
val releaseDate: String? = null,
val originCountry: List<String>? = null, val originCountry: List<String>? = null,
val genreIds: List<Int>, val genreIds: List<Int>,
val id: Int, val id: Int,
val originalTitle: String? = null, val originalTitle: String,
val originalLanguage: String, val originalLanguage: String,
val title: String?, val title: String,
val backdropPath: String? = null, val backdropPath: String? = null,
val popularity: Double, val popularity: Double,
val voteCount: Int, val voteCount: Int,
val voteAverage: Double, val voteAverage: Double,
val name: String? = null,
val originalName: String? = null,
val mediaType: String? val mediaType: String?
) )
{} {}

@ -17,6 +17,45 @@ open class Movie(
open val voteAverage: Double, 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 (genreIds != other.genreIds) 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 (video != other.video) 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 + genreIds.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 + (video?.hashCode() ?: 0)
result = 31 * result + voteAverage.hashCode()
return result
}
} }

@ -1,131 +1,88 @@
package fr.iut.pm.movieapplication.repository package fr.iut.pm.movieapplication.repository
import android.util.Log import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
import fr.iut.pm.movieapplication.api.RetrofitInstance import fr.iut.pm.movieapplication.api.RetrofitInstance
import fr.iut.pm.movieapplication.api.dtos.PopularDTO import fr.iut.pm.movieapplication.api.dtos.PopularDTO
import fr.iut.pm.movieapplication.model.media.MediaResult 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.Movie
import fr.iut.pm.movieapplication.utils.MediaResultMapper import fr.iut.pm.movieapplication.utils.MediaResultMapper
import retrofit2.Call import kotlinx.coroutines.Dispatchers
import retrofit2.Callback
import retrofit2.Response
class MovieRepository() { class MovieRepository {
fun getPopularMovies(page : Int = 1 ,callback: (List<Movie>) -> Unit ) { suspend fun getPopularMovies(page : Int = 1) : List<Movie>
{
val listMovie : MutableList<Movie> = mutableListOf() val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getPopularMovies(page = page).enqueue(object : val response = RetrofitInstance.api.getPopularMovies(page = page)
Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if(response.isSuccessful) { if(response.isSuccessful) {
Log.d("List :", response.body().toString()) val listMediaResultDTO = response.body()?.results
val popularDTO = response.body() listMediaResultDTO?.forEach {
val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach {
val movie = MediaResultMapper.mapToMovie(it) val movie = MediaResultMapper.mapToMovie(it)
listMovie.add(movie) listMovie.add(movie)
Log.d("Movie ", movie.title!!) Log.d("Movie ", movie.title!!)
} }
}
callback(listMovie)
} }
else Log.d("ERROR FAILED", response.message())
override fun onFailure(call: Call<PopularDTO>, t: Throwable) { return listMovie
Log.d("Error failure", t.message.toString())
}
})
} }
fun getNowPlayingMovies(page : Int = 1, callback: (List<Movie>) -> Unit) { suspend fun getNowPlayingMovies(page : Int = 1) : List<Movie>
{
val listMovie : MutableList<Movie> = mutableListOf() val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getNowPlayingMovies(page = page).enqueue(object : val response = RetrofitInstance.api.getNowPlayingMovies(page = page)
Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if(response.isSuccessful) { if(response.isSuccessful) {
Log.d("List :", response.body().toString()) val listMediaResultDTO = response.body()?.results
val popularDTO = response.body() listMediaResultDTO?.forEach {
val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach {
val movie = MediaResultMapper.mapToMovie(it) val movie = MediaResultMapper.mapToMovie(it)
listMovie.add(movie) listMovie.add(movie)
Log.d("Movie ", movie.title!!) Log.d("Movie ", movie.title!!)
} }
}
callback(listMovie)
} }
else Log.d("ERROR FAILED", response.message())
override fun onFailure(call: Call<PopularDTO>, t: Throwable) { return listMovie
Log.d("Error failure", t.message.toString())
}
})
} }
fun getUpcomingMovies(page : Int = 1, callback: (List<Movie>) -> Unit) { suspend fun getUpcomingMovies(page : Int = 1) : List<Movie>
{
val listMovie : MutableList<Movie> = mutableListOf() val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getUpcomingMovies(page = page).enqueue(object : val response = RetrofitInstance.api.getUpcomingMovies(page = page)
Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if(response.isSuccessful) { if(response.isSuccessful) {
Log.d("List :", response.body().toString()) val listMediaResultDTO = response.body()?.results
val popularDTO = response.body() listMediaResultDTO?.forEach {
val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach {
val movie = MediaResultMapper.mapToMovie(it) val movie = MediaResultMapper.mapToMovie(it)
listMovie.add(movie) listMovie.add(movie)
Log.d("Movie ", movie.title!!) Log.d("Movie ", movie.title!!)
} }
}
callback(listMovie)
} }
else Log.d("ERROR FAILED", response.message())
override fun onFailure(call: Call<PopularDTO>, t: Throwable) { return listMovie
Log.d("Error failure", t.message.toString())
}
})
} }
fun getTopRatedMovies(page : Int = 1, callback: (List<Movie>) -> Unit) { suspend fun getTopRatedMovies(page : Int = 1) : List<Movie>
{
val listMovie : MutableList<Movie> = mutableListOf() val listMovie : MutableList<Movie> = mutableListOf()
RetrofitInstance.api.getTopRatedMovies(page = page).enqueue(object : val response = RetrofitInstance.api.getTopRatedMovies(page = page)
Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if(response.isSuccessful) { if(response.isSuccessful) {
Log.d("List :", response.body().toString()) val listMediaResultDTO = response.body()?.results
val popularDTO = response.body() listMediaResultDTO?.forEach {
val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach {
val movie = MediaResultMapper.mapToMovie(it) val movie = MediaResultMapper.mapToMovie(it)
listMovie.add(movie) listMovie.add(movie)
Log.d("Movie ", movie.title!!) Log.d("Movie ", movie.title!!)
} }
}
callback(listMovie)
} }
else Log.d("ERROR FAILED", response.message())
override fun onFailure(call: Call<PopularDTO>, t: Throwable) { return listMovie
Log.d("Error failure", t.message.toString())
}
})
} }

@ -12,32 +12,32 @@ import retrofit2.Response
class TVShowRepository { class TVShowRepository {
fun getPopularTvShow(page : Int = 1 ,callback: (List<TvShow>) -> Unit ) { // fun getPopularTvShow(page : Int = 1 ,callback: (List<TvShow>) -> Unit ) {
//
val listMovie : MutableList<TvShow> = mutableListOf() // val listMovie : MutableList<TvShow> = mutableListOf()
//
RetrofitInstance.api.getPopularMovies(page = page).enqueue(object : // RetrofitInstance.api.getPopularMovies(page = page).enqueue(object :
Callback<PopularDTO> { // Callback<PopularDTO> {
override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) { // override fun onResponse(call: Call<PopularDTO>, response: Response<PopularDTO>) {
if (response.isSuccessful) { // if (response.isSuccessful) {
Log.d("List :", response.body().toString()) // Log.d("List :", response.body().toString())
val popularDTO = response.body() // val popularDTO = response.body()
val listMoviesDTO = popularDTO?.results // val listMoviesDTO = popularDTO?.results
listMoviesDTO?.forEach { // listMoviesDTO?.forEach {
//
val tvShow = MediaResultMapper.mapToTvShow(it) // val tvShow = MediaResultMapper.mapToTvShow(it)
listMovie.add(tvShow) // listMovie.add(tvShow)
Log.d("Movie ", tvShow.name ) // Log.d("Movie ", tvShow.name )
} // }
//
} // }
callback(listMovie) // callback(listMovie)
} // }
//
override fun onFailure(call: Call<PopularDTO>, t: Throwable) { // override fun onFailure(call: Call<PopularDTO>, t: Throwable) {
Log.d("Error failure", t.message.toString()) // Log.d("Error failure", t.message.toString())
} // }
//
}) // })
} // }
} }

@ -36,7 +36,7 @@ class MainActivity : AppCompatActivity() {
} }
R.id.movies_page -> { R.id.movies_page -> {
loadFragments(MoviesFragment(this)) loadFragments(MoviesFragment())
return@setOnItemSelectedListener true return@setOnItemSelectedListener true
} }

@ -1,70 +0,0 @@
package fr.iut.pm.movieapplication.ui.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.net.toUri
import androidx.recyclerview.widget.RecyclerView
import coil.load
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.model.media.movie.Movie
import fr.iut.pm.movieapplication.model.media.tvshow.TvShow
import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.utils.Constants.Companion.IMG_URL
class CategoryAdapter<T>(
private val context: MainActivity,
private val layoutId: Int,
private val list: List<T>
) : RecyclerView.Adapter<MediaAdapter.ViewHolder>(){
class ViewHolder(view : View) : RecyclerView.ViewHolder(view) {
val itemImage: ImageView = view.findViewById<ImageView>(R.id.item_image)
val itemName: TextView = view.findViewById<TextView>(R.id.item_name)
val itemDate: TextView = view.findViewById<TextView>(R.id.item_date)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaAdapter.ViewHolder {
val view = LayoutInflater
.from(parent.context)
.inflate(layoutId, parent, false)
return MediaAdapter.ViewHolder(view)
}
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: MediaAdapter.ViewHolder, position: Int) {
val currentItem = list[position]
if(currentItem != null) {
when(currentItem!!::class.java) {
Movie::class.java -> bindMovie(holder, currentItem as Movie)
TvShow::class.java -> bindTvShow(holder, currentItem as TvShow)
}
}
}
private fun bindTvShow(holder: MediaAdapter.ViewHolder, tvShow: TvShow) {
val imgUri = tvShow.posterPath?.let { (IMG_URL+it).toUri().buildUpon().scheme("https").build() }
holder.itemImage.load(imgUri)
holder.itemName.text = tvShow.name
holder.itemDate.text = tvShow.firstAirDate
}
private fun bindMovie(holder: MediaAdapter.ViewHolder, movie: Movie) {
val imgUri = movie.posterPath?.let { (IMG_URL+it).toUri().buildUpon().scheme("https").build() }
holder.itemImage.load(imgUri)
holder.itemName.text = movie.title
holder.itemDate.text = movie.releaseDate
}
private fun onItemClick(item : Movie) {
Log.d("item clicked", item.toString())
}
}

@ -37,11 +37,8 @@ class MediaAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = list[position] val currentItem = list[position]
when(currentItem.mediaType) { bindItem(holder, currentItem)
"movie" -> bindMovie(holder, currentItem)
"tv" -> bindTvShow(holder, currentItem)
}
Log.d("SINGLETON", GlobalImageConfig.baseUrl)
val imgUri = currentItem.posterPath?.let { val imgUri = currentItem.posterPath?.let {
(IMG_URL + it).toUri().buildUpon().scheme("https").build() (IMG_URL + it).toUri().buildUpon().scheme("https").build()
} }
@ -55,15 +52,10 @@ class MediaAdapter(
} }
// If the item is a Movie // If the item is a Movie
private fun bindMovie(holder: ViewHolder, currentItem: MediaResult) { private fun bindItem(holder: ViewHolder, currentItem: MediaResult) {
holder.itemName.text = currentItem.title holder.itemName.text = currentItem.title
holder.itemDate.text = currentItem.releaseDate holder.itemDate.text = currentItem.releaseDate
} }
// If the item is a TvShow
private fun bindTvShow(holder: ViewHolder, currentItem: MediaResult) {
holder.itemName.text = currentItem.name
holder.itemDate.text = currentItem.firstAirDate
}
private fun onItemClick(item : MediaResult) { private fun onItemClick(item : MediaResult) {
Log.d("item clicked", item.toString()) Log.d("item clicked", item.toString())

@ -0,0 +1,47 @@
package fr.iut.pm.movieapplication.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.net.toUri
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.databinding.ItemMovieCategoryBinding
import fr.iut.pm.movieapplication.model.media.movie.Movie
import fr.iut.pm.movieapplication.utils.Constants
class MovieAdapter : ListAdapter<Movie, MovieAdapter.ViewHolder>(DiffUtilDogCallback) {
private object DiffUtilDogCallback : 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) :
RecyclerView.ViewHolder(binding.root) {
val movie : Movie? get() = binding.movie
init {
itemView.setOnClickListener {}
}
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)))
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(getItem(position))
}

@ -10,7 +10,6 @@ import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.ui.activity.MainActivity import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.ui.adapter.MediaAdapter import fr.iut.pm.movieapplication.ui.adapter.MediaAdapter
import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration import fr.iut.pm.movieapplication.ui.adapter.HomeItemDecoration
import fr.iut.pm.movieapplication.ui.adapter.CategoryAdapter
import fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM import fr.iut.pm.movieapplication.ui.viewmodel.HomeSectionsVM
class HomeSectionsFragment( class HomeSectionsFragment(
@ -30,11 +29,11 @@ class HomeSectionsFragment(
} }
//get the popularity RecyclerView //get the popularity RecyclerView
context.movieRepository.getPopularMovies { // context.movieRepository.getPopularMovies {
val homePopularityRecyclerView = view?.findViewById<RecyclerView>(R.id.home_popularity_recycler_view) // val homePopularityRecyclerView = view?.findViewById<RecyclerView>(R.id.home_popularity_recycler_view)
homePopularityRecyclerView?.adapter = CategoryAdapter(context,R.layout.item_horizontal_home_page,it) // homePopularityRecyclerView?.adapter = CategoryAdapter(context,R.layout.item_horizontal_home_page,it)
homePopularityRecyclerView?.addItemDecoration(HomeItemDecoration()) // homePopularityRecyclerView?.addItemDecoration(HomeItemDecoration())
} // }
//get the free RecyclerView //get the free RecyclerView
val homeFreeRecyclerView = view?.findViewById<RecyclerView>(R.id.home_free_recycler_view) val homeFreeRecyclerView = view?.findViewById<RecyclerView>(R.id.home_free_recycler_view)
homeFreeRecyclerView?.adapter = MediaAdapter(context,R.layout.item_horizontal_home_page,ArrayList()) homeFreeRecyclerView?.adapter = MediaAdapter(context,R.layout.item_horizontal_home_page,ArrayList())

@ -1,6 +1,7 @@
package fr.iut.pm.movieapplication.ui.fragments package fr.iut.pm.movieapplication.ui.fragments
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,19 +10,17 @@ import android.widget.ArrayAdapter
import android.widget.Spinner import android.widget.Spinner
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.iut.pm.movieapplication.R import fr.iut.pm.movieapplication.R
import fr.iut.pm.movieapplication.databinding.FragmentMoviesBinding
import fr.iut.pm.movieapplication.model.media.movie.Movie import fr.iut.pm.movieapplication.model.media.movie.Movie
import fr.iut.pm.movieapplication.repository.MovieRepository import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter
import fr.iut.pm.movieapplication.ui.activity.MainActivity
import fr.iut.pm.movieapplication.ui.adapter.CategoryAdapter
import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVM import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVM
import fr.iut.pm.movieapplication.ui.viewmodel.MoviesVMFactory
import fr.iut.pm.movieapplication.utils.Constants.Companion.PAGE_SIZE import fr.iut.pm.movieapplication.utils.Constants.Companion.PAGE_SIZE
class MoviesFragment( class MoviesFragment(
private val context : MainActivity
) : Fragment() { ) : Fragment() {
private var isLoading = false private var isLoading = false
@ -29,150 +28,53 @@ class MoviesFragment(
private var currentPage = 1 private var currentPage = 1
private var currentList : MutableList<Movie> = mutableListOf() private var currentList : MutableList<Movie> = mutableListOf()
private val moviesVM: MoviesVM by viewModels{ MoviesVMFactory(repository = MovieRepository())} private val moviesVM by viewModels<MoviesVM>()
lateinit var moviesRecyclerView : RecyclerView
lateinit var spinner: Spinner
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_movies, container, false)
// Get the RecyclerView
moviesRecyclerView = view.findViewById<RecyclerView>(R.id.movies_item_recycler_view)
// Initialized the data inside our RecyclerView
// Create the ScrollListener
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = moviesRecyclerView.layoutManager as GridLayoutManager
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
// If we are not already loading data and it's not the last page
if(!isLoading && !isLastPage) {
if(visibleItemCount + firstVisibleItemPosition >= totalItemCount
&& firstVisibleItemPosition >= 0
&& totalItemCount >= PAGE_SIZE
) {
loadMoreMovies()
}
}
}
}
// Add the ScrollLister created before to our RecyclerView lateinit var spinner: Spinner
moviesRecyclerView.addOnScrollListener(scrollListener)
spinner = view.findViewById(R.id.category_spinner) override fun onCreateView(
configSpinner(spinner) inflater: LayoutInflater, container: ViewGroup?,
return view savedInstanceState: Bundle?
} ): View {
val binding = FragmentMoviesBinding.inflate(inflater)
binding.moviesVM = moviesVM
binding.lifecycleOwner = viewLifecycleOwner
private fun configSpinner(spinner: Spinner) { val adapter = ArrayAdapter.createFromResource(
ArrayAdapter.createFromResource( requireContext(),
context,
R.array.movie_filter, R.array.movie_filter,
android.R.layout.simple_spinner_item android.R.layout.simple_spinner_item
).also { adapter -> )
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter with(binding.categorySpinner)
} {
this.adapter = adapter
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected( override fun onItemSelected(
parent: AdapterView<*>?, parent: AdapterView<*>?,
view: View?, view: View?,
position: Int, position: Int,
id: Long id: Long
) { ) {
when(position) { moviesVM.getDataFilter(selectedItem.toString())
0 -> {
currentList.clear()
context.movieRepository.getPopularMovies { listMovies ->
currentList.addAll(0,listMovies)
moviesRecyclerView.adapter = CategoryAdapter(context, R.layout.item_vertical_fragment, currentList)
moviesRecyclerView.layoutManager = GridLayoutManager (context, 3)
}
} }
1 -> {
currentList.clear()
context.movieRepository.getNowPlayingMovies { movies: List<Movie> ->
currentList.addAll(0,movies)
moviesRecyclerView.adapter = CategoryAdapter(context, R.layout.item_vertical_fragment, currentList)
moviesRecyclerView.layoutManager = GridLayoutManager (context, 3)
}
}
2 -> {
currentList.clear()
context.movieRepository.getUpcomingMovies { movies: List<Movie> ->
currentList.addAll(0,movies)
moviesRecyclerView.adapter = CategoryAdapter(context, R.layout.item_vertical_fragment, currentList)
moviesRecyclerView.layoutManager = GridLayoutManager (context, 3)
}
}
3 -> {
currentList.clear()
context.movieRepository.getTopRatedMovies { movies ->
currentList.addAll(0,movies)
moviesRecyclerView.adapter = CategoryAdapter(context, R.layout.item_vertical_fragment, currentList)
moviesRecyclerView.layoutManager = GridLayoutManager (context, 3)
}
}
}
}
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
} }
}
/**
* Method to load data when the user reaches the bottom of the view
*/
private fun loadMoreMovies() {
isLoading = true
currentPage += 1
if(currentPage == 1000) isLastPage = true
val start = currentList.size
when(spinner.selectedItemPosition) {
0 -> {
context.movieRepository.getPopularMovies(currentPage) { listMovies ->
currentList.addAll(start, listMovies)
moviesRecyclerView.adapter?.notifyItemRangeChanged(start, listMovies.size)
}
}
1 -> {
context.movieRepository.getNowPlayingMovies(currentPage) { listMovies ->
currentList.addAll(start, listMovies)
moviesRecyclerView.adapter?.notifyItemRangeChanged(start, listMovies.size)
}
}
2 -> {
context.movieRepository.getUpcomingMovies(currentPage) { listMovies ->
currentList.addAll(start, listMovies)
moviesRecyclerView.adapter?.notifyItemRangeChanged(start, listMovies.size)
}
}
3 -> {
context.movieRepository.getTopRatedMovies(currentPage) { listMovies ->
currentList.addAll(start, listMovies)
moviesRecyclerView.adapter?.notifyItemRangeChanged(start, listMovies.size)
} }
} }
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
isLoading = false super.onViewCreated(view, savedInstanceState)
moviesVM.getMoviesLiveData().observe(viewLifecycleOwner) {
moviesVM.moviesAdapter.submitList(it)
}
} }
} }

@ -1,27 +1,93 @@
package fr.iut.pm.movieapplication.ui.viewmodel package fr.iut.pm.movieapplication.ui.viewmodel
import androidx.lifecycle.* import androidx.lifecycle.*
import fr.iut.pm.movieapplication.model.media.movie.MovieDetails 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.MovieRepository import fr.iut.pm.movieapplication.repository.MovieRepository
import fr.iut.pm.movieapplication.ui.adapter.MovieAdapter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MoviesVM(private val repository: MovieRepository) : ViewModel() { class MoviesVM() : ViewModel() {
//Movie repository
private val repository = MovieRepository()
//Live data
private var _moviesLiveData : MutableLiveData<List<Movie>> = MutableLiveData<List<Movie>>()
fun getMoviesLiveData() : LiveData<List<Movie>> = _moviesLiveData
private val _popularMovies = MutableLiveData<List<MovieDetails>>() private var currentFilter = ""
val popularMovies : LiveData<List<MovieDetails>> = _popularMovies val moviesAdapter = MovieAdapter()
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
init { if(lastVisibleItemPosition == totalItemCount -1) {
//loadData()
++currentPage
getMoreData(currentPage)
}
} }
} }
private var currentPage = 1
fun getDataFilter(filter : String) {
//_moviesLiveData.value = mutableListOf()
currentFilter = filter
currentPage = 1
when(currentFilter) {
"Populaires" -> viewModelScope.launch {
_moviesLiveData.postValue(repository.getPopularMovies())
}
"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())
}
}
}
fun getMoreData(page : Int = 1) {
var movies : List<Movie>
when(currentFilter) {
"Populaires" -> viewModelScope.launch {
movies = _moviesLiveData.value?.plus(repository.getPopularMovies(page)) ?: listOf()
_moviesLiveData.postValue(movies)
}
"Du moment" -> viewModelScope.launch {
movies = _moviesLiveData.value?.plus(repository.getNowPlayingMovies(page)) ?: listOf()
_moviesLiveData.postValue(movies)
}
"À venir" -> viewModelScope.launch {
movies = _moviesLiveData.value?.plus(repository.getUpcomingMovies(page)) ?: listOf()
_moviesLiveData.postValue(movies)
}
"Les mieux notés" -> viewModelScope.launch {
movies = _moviesLiveData.value?.plus(repository.getTopRatedMovies(page)) ?: listOf()
_moviesLiveData.postValue(movies)
}
}
}
}
class MoviesVMFactory : ViewModelProvider.Factory {
class MoviesVMFactory(
private val repository: MovieRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MoviesVM(repository) as T return MoviesVM() as T
} }
} }

@ -30,7 +30,7 @@ object MediaResultMapper {
posterPath = mediaResultDTO.posterPath, posterPath = mediaResultDTO.posterPath,
adult = !mediaResultDTO.adult, adult = !mediaResultDTO.adult,
overview = mediaResultDTO.overview, overview = mediaResultDTO.overview,
releaseDate = mediaResultDTO.releaseDate!!, releaseDate = mediaResultDTO.releaseDate ?: "",
genreIds = mediaResultDTO.genreIds, genreIds = mediaResultDTO.genreIds,
id = mediaResultDTO.id, id = mediaResultDTO.id,
originalTitle = mediaResultDTO.originalTitle!!, originalTitle = mediaResultDTO.originalTitle!!,
@ -49,20 +49,17 @@ object MediaResultMapper {
posterPath = mediaResultDTO.posterPath, posterPath = mediaResultDTO.posterPath,
adult = !mediaResultDTO.adult, adult = !mediaResultDTO.adult,
overview = mediaResultDTO.overview, overview = mediaResultDTO.overview,
firstAirDate = mediaResultDTO.firstAirDate, releaseDate = mediaResultDTO.releaseDate ?: mediaResultDTO.firstAirDate!! ,
releaseDate = mediaResultDTO.releaseDate,
originCountry = mediaResultDTO.originCountry, originCountry = mediaResultDTO.originCountry,
genreIds = mediaResultDTO.genreIds, genreIds = mediaResultDTO.genreIds,
id = mediaResultDTO.id, id = mediaResultDTO.id,
originalTitle = mediaResultDTO.originalTitle, originalTitle = mediaResultDTO.originalTitle ?: mediaResultDTO.originalName!!, //if it's not a movie also it's a tvshow
originalLanguage = mediaResultDTO.originalLanguage, originalLanguage = mediaResultDTO.originalLanguage,
title = mediaResultDTO.title, title = mediaResultDTO.title ?: mediaResultDTO.name!!, //if it's not a movie also it's a tvshow
backdropPath = mediaResultDTO.backdropPath, backdropPath = mediaResultDTO.backdropPath,
popularity = mediaResultDTO.popularity, popularity = mediaResultDTO.popularity,
voteCount = mediaResultDTO.voteCount, voteCount = mediaResultDTO.voteCount,
voteAverage = mediaResultDTO.voteAverage, voteAverage = mediaResultDTO.voteAverage,
name = mediaResultDTO.name,
originalName = mediaResultDTO.originalName,
mediaType = mediaResultDTO.mediaType mediaType = mediaResultDTO.mediaType
) )
} }

@ -1,9 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">
<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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto">
<Spinner <Spinner
android:id="@+id/category_spinner" android:id="@+id/category_spinner"
@ -16,11 +30,27 @@
android:layout_marginBottom="@dimen/default_margin" android:layout_marginBottom="@dimen/default_margin"
android:background="@color/light_grey" android:background="@color/light_grey"
android:layout_marginRight="@dimen/default_margin" /> android:layout_marginRight="@dimen/default_margin" />
<ProgressBar
android:id="@+id/movie_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/movies_item_recycler_view" android:id="@+id/movies_item_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="@dimen/default_margin" android:layout_marginBottom="@dimen/default_margin"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
android:adapter="@{moviesVM.moviesAdapter}"
app:onScrollListener = "@{moviesVM.scrollListener}"
tools:listitem="@layout/item_movie_category"
/> />
</LinearLayout> </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="movie"
type="fr.iut.pm.movieapplication.model.media.movie.Movie" />
</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="@{movie.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="@{movie.releaseDate}"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Loading…
Cancel
Save