diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/MainDatabase.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/MainDatabase.kt index 0c15158..6aacfdb 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/MainDatabase.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/MainDatabase.kt @@ -7,22 +7,24 @@ import com.adesso.movee.data.local.database.dao.MovieGenreCrossRefDao import com.adesso.movee.data.local.database.dao.MovieGenreDao import com.adesso.movee.data.local.database.dao.NowPlayingMovieDao import com.adesso.movee.data.local.database.dao.NowPlayingMovieIdPageDao -import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdDao +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowDao +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdPageDao import com.adesso.movee.data.local.database.dao.PopularMovieDao import com.adesso.movee.data.local.database.dao.PopularMovieIdPageDao -import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdDao -import com.adesso.movee.data.local.database.dao.TvShowDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdPageDao import com.adesso.movee.data.local.database.dao.TvShowGenreCrossRefDao import com.adesso.movee.data.local.database.dao.TvShowGenreDao import com.adesso.movee.data.local.database.entity.MovieGenreCrossRefEntity import com.adesso.movee.data.local.database.entity.MovieGenreEntity import com.adesso.movee.data.local.database.entity.NowPlayingMovieEntity import com.adesso.movee.data.local.database.entity.NowPlayingMovieIdPageEntity -import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdPageEntity import com.adesso.movee.data.local.database.entity.PopularMovieEntity import com.adesso.movee.data.local.database.entity.PopularMovieIdPageEntity -import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdEntity -import com.adesso.movee.data.local.database.entity.TvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdPageEntity import com.adesso.movee.data.local.database.entity.TvShowGenreCrossRefEntity import com.adesso.movee.data.local.database.entity.TvShowGenreEntity import com.adesso.movee.internal.util.typeconverter.DateTypeConverter @@ -36,13 +38,14 @@ import com.adesso.movee.internal.util.typeconverter.GenreConverter NowPlayingMovieIdPageEntity::class, MovieGenreEntity::class, MovieGenreCrossRefEntity::class, - TvShowEntity::class, - TopRatedTvShowIdEntity::class, - NowPlayingTvShowIdEntity::class, + NowPlayingTvShowEntity::class, + NowPlayingTvShowIdPageEntity::class, + TopRatedTvShowEntity::class, + TopRatedTvShowIdPageEntity::class, TvShowGenreEntity::class, TvShowGenreCrossRefEntity::class ], - version = 3 + version = 4 ) @TypeConverters( DateTypeConverter::class, @@ -57,9 +60,10 @@ abstract class MainDatabase : RoomDatabase() { abstract fun movieGenreDao(): MovieGenreDao abstract fun movieGenreCrossRefDao(): MovieGenreCrossRefDao - abstract fun tvShowDao(): TvShowDao - abstract fun topRatedTvShowIdDao(): TopRatedTvShowIdDao - abstract fun nowPlayingTvShowIdDao(): NowPlayingTvShowIdDao + abstract fun topRatedTvShowDao(): TopRatedTvShowDao + abstract fun nowPlayingTvShowDao(): NowPlayingTvShowDao + abstract fun topRatedTvShowIdPageDao(): TopRatedTvShowIdPageDao + abstract fun nowPlayingTvShowIdPageDao(): NowPlayingTvShowIdPageDao abstract fun tvShowGenreDao(): TvShowGenreDao abstract fun tvShowGenreCrossRefDao(): TvShowGenreCrossRefDao } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieDao.kt index a177304..760efe7 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieDao.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieDao.kt @@ -10,16 +10,11 @@ import com.adesso.movee.data.local.database.entity.NowPlayingMovieWithGenres @Dao abstract class NowPlayingMovieDao : BaseDao { - @Transaction - @Query("SELECT * FROM now_playing_movie WHERE id IN (:movieIds)") - abstract suspend fun getMoviesWithGenresByIds(movieIds: List): - List - @Transaction @Query("SELECT * FROM now_playing_movie") - abstract fun getMoviesWithGenresPagingSource(): PagingSource + abstract fun getPagingSource(): PagingSource @Transaction @Query("DELETE FROM now_playing_movie") - abstract fun clearNowPlayingMoviesWithGenres() + abstract suspend fun clear() } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieIdPageDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieIdPageDao.kt index a693785..e86e2de 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieIdPageDao.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingMovieIdPageDao.kt @@ -8,9 +8,9 @@ import com.adesso.movee.data.local.database.entity.TABLE_NOW_PLAYING_MOVIE_ID_PA @Dao abstract class NowPlayingMovieIdPageDao : BaseDao { - @Query("SELECT id FROM $TABLE_NOW_PLAYING_MOVIE_ID_PAGE") - abstract suspend fun getIds(): List + @Query("SELECT page FROM $TABLE_NOW_PLAYING_MOVIE_ID_PAGE") + abstract suspend fun getPages(): List @Query("DELETE FROM $TABLE_NOW_PLAYING_MOVIE_ID_PAGE") - abstract fun clearNowPlayingMovieIds() + abstract suspend fun clear() } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowDao.kt new file mode 100644 index 0000000..b045f98 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowDao.kt @@ -0,0 +1,20 @@ +package com.adesso.movee.data.local.database.dao + +import androidx.paging.PagingSource +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowWithGenres + +@Dao +abstract class NowPlayingTvShowDao : BaseDao { + + @Transaction + @Query("SELECT * FROM now_playing_tv_show") + abstract fun getPagingSource(): PagingSource + + @Transaction + @Query("DELETE FROM now_playing_tv_show") + abstract suspend fun clear() +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdDao.kt deleted file mode 100644 index 81d5306..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdDao.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.adesso.movee.data.local.database.dao - -import androidx.room.Dao -import androidx.room.Query -import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdEntity - -@Dao -abstract class NowPlayingTvShowIdDao : BaseDao { - - @Query("SELECT id FROM now_playing_tv_show_id") - abstract suspend fun getIds(): List -} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdPageDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdPageDao.kt new file mode 100644 index 0000000..2e5cdd5 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/NowPlayingTvShowIdPageDao.kt @@ -0,0 +1,16 @@ +package com.adesso.movee.data.local.database.dao + +import androidx.room.Dao +import androidx.room.Query +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdPageEntity +import com.adesso.movee.data.local.database.entity.TABLE_NOW_PLAYING_TV_SHOW_ID_PAGE + +@Dao +abstract class NowPlayingTvShowIdPageDao : BaseDao { + + @Query("SELECT page FROM $TABLE_NOW_PLAYING_TV_SHOW_ID_PAGE") + abstract suspend fun getPages(): List + + @Query("DELETE FROM $TABLE_NOW_PLAYING_TV_SHOW_ID_PAGE") + abstract suspend fun clear() +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieDao.kt index 6c630b4..b6a84d3 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieDao.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieDao.kt @@ -10,16 +10,11 @@ import com.adesso.movee.data.local.database.entity.PopularMovieWithGenres @Dao abstract class PopularMovieDao : BaseDao { - @Transaction - @Query("SELECT * FROM popular_movie WHERE id IN (:movieIds)") - abstract suspend fun getMoviesWithGenresByIds(movieIds: List): - List - @Transaction @Query("SELECT * FROM popular_movie") - abstract fun getMoviesWithGenresPagingSource(): PagingSource + abstract fun getPagingSource(): PagingSource @Transaction @Query("DELETE FROM popular_movie") - abstract fun clearPopularMoviesWithGenres() + abstract suspend fun clear() } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieIdPageDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieIdPageDao.kt index 58a22a4..fb14eaa 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieIdPageDao.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/PopularMovieIdPageDao.kt @@ -8,12 +8,9 @@ import com.adesso.movee.data.local.database.entity.TABLE_POPULAR_MOVIE_ID_PAGE @Dao abstract class PopularMovieIdPageDao : BaseDao { - @Query("SELECT id FROM $TABLE_POPULAR_MOVIE_ID_PAGE") - abstract suspend fun getIds(): List - @Query("SELECT page FROM $TABLE_POPULAR_MOVIE_ID_PAGE") - abstract suspend fun getPopularMoviePages(): List + abstract suspend fun getPages(): List @Query("DELETE FROM $TABLE_POPULAR_MOVIE_ID_PAGE") - abstract fun clearPopularMovieIds() + abstract suspend fun clear() } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowDao.kt new file mode 100644 index 0000000..08b5886 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowDao.kt @@ -0,0 +1,20 @@ +package com.adesso.movee.data.local.database.dao + +import androidx.paging.PagingSource +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import com.adesso.movee.data.local.database.entity.TopRatedTvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowWithGenres + +@Dao +abstract class TopRatedTvShowDao : BaseDao { + + @Transaction + @Query("SELECT * FROM top_rated_tv_show") + abstract fun getPagingSource(): PagingSource + + @Transaction + @Query("DELETE FROM top_rated_tv_show") + abstract suspend fun clear() +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdDao.kt deleted file mode 100644 index 0ef5633..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdDao.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.adesso.movee.data.local.database.dao - -import androidx.room.Dao -import androidx.room.Query -import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdEntity - -@Dao -abstract class TopRatedTvShowIdDao : BaseDao { - - @Query("SELECT id FROM top_rated_tv_show_id") - abstract suspend fun getIds(): List -} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdPageDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdPageDao.kt new file mode 100644 index 0000000..b6dd6d1 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TopRatedTvShowIdPageDao.kt @@ -0,0 +1,15 @@ +package com.adesso.movee.data.local.database.dao + +import androidx.room.Dao +import androidx.room.Query +import com.adesso.movee.data.local.database.entity.TABLE_TOP_RATED_TV_SHOW_ID_PAGE +import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdPageEntity + +@Dao +abstract class TopRatedTvShowIdPageDao : BaseDao { + @Query("SELECT page FROM $TABLE_TOP_RATED_TV_SHOW_ID_PAGE") + abstract suspend fun getPages(): List + + @Query("DELETE FROM $TABLE_TOP_RATED_TV_SHOW_ID_PAGE") + abstract suspend fun clear() +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TvShowDao.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TvShowDao.kt deleted file mode 100644 index 5bec102..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/dao/TvShowDao.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.adesso.movee.data.local.database.dao - -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import com.adesso.movee.data.local.database.entity.TvShowEntity -import com.adesso.movee.data.local.database.entity.TvShowWithGenres - -@Dao -abstract class TvShowDao : BaseDao { - - @Transaction - @Query("SELECT * FROM tv_show WHERE id IN (:tvShowIds)") - abstract suspend fun getTvShowsWithGenresByIds(tvShowIds: List): List -} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/BaseIdEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/BaseIdEntity.kt deleted file mode 100644 index e5b3d59..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/BaseIdEntity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.adesso.movee.data.local.database.entity - -interface BaseIdEntity { - val id: Long -} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingMovieEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingMovieEntity.kt index b1fbcec..7cb94cf 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingMovieEntity.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingMovieEntity.kt @@ -4,13 +4,11 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.adesso.movee.internal.util.Image -import com.adesso.movee.uimodel.MovieGenreUiModel -import com.adesso.movee.uimodel.MovieUiModel import java.util.Date @Entity(tableName = "now_playing_movie") data class NowPlayingMovieEntity( - @ColumnInfo(name = "id") @PrimaryKey val id: Long, + @ColumnInfo(name = "id") val id: Long, @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "overview") val overview: String, @ColumnInfo(name = "genres") val genreIds: List, @@ -21,17 +19,6 @@ data class NowPlayingMovieEntity( @ColumnInfo(name = "adult") val isAdult: Boolean, @ColumnInfo(name = "release_date") val releaseDate: Date? ) { - - fun toUiModel(genres: List) = MovieUiModel( - id = id, - title = title, - overview = overview, - genres = genres, - posterPath = posterPath, - backdropPath = backdropPath, - popularity = popularity, - average = average, - isAdult = isAdult, - releaseDate = releaseDate - ) + @PrimaryKey(autoGenerate = true) + var uId: Int = 0 } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowEntity.kt similarity index 56% rename from app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowEntity.kt rename to app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowEntity.kt index cc52a4e..e8ea37c 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowEntity.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowEntity.kt @@ -4,13 +4,11 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.adesso.movee.internal.util.Image -import com.adesso.movee.uimodel.TvShowGenreUiModel -import com.adesso.movee.uimodel.TvShowUiModel import java.util.Date -@Entity(tableName = "tv_show") -data class TvShowEntity( - @ColumnInfo(name = "id") @PrimaryKey val id: Long, +@Entity(tableName = "now_playing_tv_show") +data class NowPlayingTvShowEntity( + @ColumnInfo(name = "id") val id: Long, @ColumnInfo(name = "name") val title: String, @ColumnInfo(name = "overview") val overview: String, @ColumnInfo(name = "genre_ids") val genreIds: List, @@ -20,15 +18,6 @@ data class TvShowEntity( @ColumnInfo(name = "vote_average") val average: Double, @ColumnInfo(name = "first_air_date") val releaseDate: Date? ) { - fun toUiModel(genres: List) = TvShowUiModel( - id = id, - title = title, - overview = overview, - genres = genres, - posterPath = posterPath, - backdropPath = backdropPath, - popularity = popularity, - average = average, - releaseDate = releaseDate - ) + @PrimaryKey(autoGenerate = true) + var uId: Int = 0 } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdEntity.kt deleted file mode 100644 index 5daf3f1..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdEntity.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.adesso.movee.data.local.database.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "now_playing_tv_show_id") -data class NowPlayingTvShowIdEntity( - @ColumnInfo(name = "id") @PrimaryKey override val id: Long -) : BaseIdEntity diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdPageEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdPageEntity.kt new file mode 100644 index 0000000..e27e46d --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowIdPageEntity.kt @@ -0,0 +1,16 @@ +package com.adesso.movee.data.local.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +const val TABLE_NOW_PLAYING_TV_SHOW_ID_PAGE = "now_playing_tv_show_id_page" + +@Entity(tableName = TABLE_NOW_PLAYING_TV_SHOW_ID_PAGE) +class NowPlayingTvShowIdPageEntity( + @ColumnInfo(name = "id") val id: Long, + @ColumnInfo(name = "page") val page: Int +) { + @PrimaryKey(autoGenerate = true) + var uId: Int = 0 +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowWithGenres.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowWithGenres.kt new file mode 100644 index 0000000..64f02eb --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/NowPlayingTvShowWithGenres.kt @@ -0,0 +1,31 @@ +package com.adesso.movee.data.local.database.entity + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import com.adesso.movee.internal.extension.convertTvShowGenres +import com.adesso.movee.uimodel.TvShowUiModel + +data class NowPlayingTvShowWithGenres( + @Embedded val tvShow: NowPlayingTvShowEntity, + @Relation( + parentColumn = "id", + entityColumn = "genre_id", + associateBy = Junction( + value = TvShowGenreCrossRefEntity::class + ) + ) + val genres: List +) { + fun toUiModel() = TvShowUiModel( + id = tvShow.id, + title = tvShow.title, + overview = tvShow.overview, + genres = genres.convertTvShowGenres(), + posterPath = tvShow.posterPath, + backdropPath = tvShow.backdropPath, + popularity = tvShow.popularity, + average = tvShow.average, + releaseDate = tvShow.releaseDate + ) +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/PopularMovieEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/PopularMovieEntity.kt index d6eccac..56b7a10 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/PopularMovieEntity.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/PopularMovieEntity.kt @@ -4,8 +4,6 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.adesso.movee.internal.util.Image -import com.adesso.movee.uimodel.MovieGenreUiModel -import com.adesso.movee.uimodel.MovieUiModel import java.util.Date @Entity(tableName = "popular_movie") @@ -23,17 +21,4 @@ data class PopularMovieEntity( ) { @PrimaryKey(autoGenerate = true) var uId: Int = 0 - - fun toUiModel(genres: List) = MovieUiModel( - id = id, - title = title, - overview = overview, - genres = genres, - posterPath = posterPath, - backdropPath = backdropPath, - popularity = popularity, - average = average, - isAdult = isAdult, - releaseDate = releaseDate - ) } diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowEntity.kt new file mode 100644 index 0000000..b8a66c1 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowEntity.kt @@ -0,0 +1,23 @@ +package com.adesso.movee.data.local.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.adesso.movee.internal.util.Image +import java.util.Date + +@Entity(tableName = "top_rated_tv_show") +class TopRatedTvShowEntity( + @ColumnInfo(name = "id") val id: Long, + @ColumnInfo(name = "name") val title: String, + @ColumnInfo(name = "overview") val overview: String, + @ColumnInfo(name = "genre_ids") val genreIds: List, + @ColumnInfo(name = "poster_path") @Image val posterPath: String?, + @ColumnInfo(name = "backdrop_path") @Image val backdropPath: String?, + @ColumnInfo(name = "popularity") val popularity: Double, + @ColumnInfo(name = "vote_average") val average: Double, + @ColumnInfo(name = "first_air_date") val releaseDate: Date? +) { + @PrimaryKey(autoGenerate = true) + var uId: Int = 0 +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdEntity.kt deleted file mode 100644 index 4a59822..0000000 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdEntity.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.adesso.movee.data.local.database.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "top_rated_tv_show_id") -data class TopRatedTvShowIdEntity( - @ColumnInfo(name = "id") @PrimaryKey override val id: Long -) : BaseIdEntity diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdPageEntity.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdPageEntity.kt new file mode 100644 index 0000000..6daa7c1 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowIdPageEntity.kt @@ -0,0 +1,16 @@ +package com.adesso.movee.data.local.database.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +const val TABLE_TOP_RATED_TV_SHOW_ID_PAGE = "top_rated_tv_show_id_page" + +@Entity(tableName = TABLE_TOP_RATED_TV_SHOW_ID_PAGE) +data class TopRatedTvShowIdPageEntity( + @ColumnInfo(name = "id") val id: Long, + @ColumnInfo(name = "page") val page: Int +) { + @PrimaryKey(autoGenerate = true) + var uId: Int = 0 +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowWithGenres.kt b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowWithGenres.kt similarity index 91% rename from app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowWithGenres.kt rename to app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowWithGenres.kt index 84da505..9cc044e 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TvShowWithGenres.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/database/entity/TopRatedTvShowWithGenres.kt @@ -6,8 +6,8 @@ import androidx.room.Relation import com.adesso.movee.internal.extension.convertTvShowGenres import com.adesso.movee.uimodel.TvShowUiModel -data class TvShowWithGenres( - @Embedded val tvShow: TvShowEntity, +class TopRatedTvShowWithGenres( + @Embedded val tvShow: TopRatedTvShowEntity, @Relation( parentColumn = "id", entityColumn = "genre_id", diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/datasource/MovieLocalDataSource.kt b/app/src/main/kotlin/com/adesso/movee/data/local/datasource/MovieLocalDataSource.kt index 6ff9958..9790d2c 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/datasource/MovieLocalDataSource.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/datasource/MovieLocalDataSource.kt @@ -31,38 +31,29 @@ class MovieLocalDataSource @Inject constructor( } fun getPopularMoviesWithGenresPagingSource(): PagingSource { - return popularMovieDao.getMoviesWithGenresPagingSource() + return popularMovieDao.getPagingSource() } - fun clearPopularMovieData() { - popularMovieDao.clearPopularMoviesWithGenres() - popularMovieIdPageDao.clearPopularMovieIds() + fun getNowPlayingMoviesWithGenresPagingSource(): PagingSource { + return nowPlayingMovieDao.getPagingSource() } - fun clearNowPlayingMovieData() { // Will be used for now playing movies paging. - nowPlayingMovieDao.clearNowPlayingMoviesWithGenres() - nowPlayingMovieIdPageDao.clearNowPlayingMovieIds() + suspend fun clearPopularMovieData() { + popularMovieDao.clear() + popularMovieIdPageDao.clear() } - suspend fun getLastPageInDataSource(): Int? { - return popularMovieIdPageDao.getPopularMoviePages().lastOrNull() + suspend fun clearNowPlayingMovieData() { // Will be used for now playing movies paging. + nowPlayingMovieDao.clear() + nowPlayingMovieIdPageDao.clear() } - suspend fun getPopularMoviesWithGenres(movieIds: List): List { - return popularMovieDao.getMoviesWithGenresByIds(movieIds) + suspend fun getPopularMoviesLastPage(): Int? { + return popularMovieIdPageDao.getPages().lastOrNull() } - suspend fun getNowPlayingMoviesWithGenres(movieIds: List): - List { - return nowPlayingMovieDao.getMoviesWithGenresByIds(movieIds) - } - - suspend fun getPopularMovieIds(): List { - return popularMovieIdPageDao.getIds() - } - - suspend fun getNowPlayingMovieIds(): List { - return nowPlayingMovieIdPageDao.getIds() + suspend fun getMovieNowPlayingLastPage(): Int? { + return nowPlayingMovieIdPageDao.getPages().lastOrNull() } suspend fun insertGenres(genres: List) { diff --git a/app/src/main/kotlin/com/adesso/movee/data/local/datasource/TvShowLocalDataSource.kt b/app/src/main/kotlin/com/adesso/movee/data/local/datasource/TvShowLocalDataSource.kt index 418b225..89666cd 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/local/datasource/TvShowLocalDataSource.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/local/datasource/TvShowLocalDataSource.kt @@ -1,40 +1,61 @@ package com.adesso.movee.data.local.datasource -import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdDao -import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdDao -import com.adesso.movee.data.local.database.dao.TvShowDao +import androidx.paging.PagingSource +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowDao +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdPageDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdPageDao import com.adesso.movee.data.local.database.dao.TvShowGenreCrossRefDao import com.adesso.movee.data.local.database.dao.TvShowGenreDao -import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdEntity -import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdEntity -import com.adesso.movee.data.local.database.entity.TvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdPageEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowWithGenres +import com.adesso.movee.data.local.database.entity.TopRatedTvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdPageEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowWithGenres import com.adesso.movee.data.local.database.entity.TvShowGenreCrossRefEntity import com.adesso.movee.data.local.database.entity.TvShowGenreEntity -import com.adesso.movee.data.local.database.entity.TvShowWithGenres import javax.inject.Inject class TvShowLocalDataSource @Inject constructor( - private val tvShowDao: TvShowDao, - private val topRatedTvShowIdDao: TopRatedTvShowIdDao, - private val nowPlayingTvShowIdDao: NowPlayingTvShowIdDao, + private val nowPlayingTvShowDao: NowPlayingTvShowDao, + private val topRatedTvShowDao: TopRatedTvShowDao, + private val topRatedTvShowIdPageDao: TopRatedTvShowIdPageDao, + private val nowPlayingTvShowIdPageDao: NowPlayingTvShowIdPageDao, private val tvShowGenreDao: TvShowGenreDao, private val tvShowGenreCrossRefDao: TvShowGenreCrossRefDao ) { - suspend fun doTvShowGenresExist(): Boolean { - return tvShowGenreDao.doTvShowGenresExist() + suspend fun clearNowPlayingTvShowsData() { + nowPlayingTvShowDao.clear() + nowPlayingTvShowIdPageDao.clear() } - suspend fun getTvShowsWithGenres(tvShowIds: List): List { - return tvShowDao.getTvShowsWithGenresByIds(tvShowIds) + suspend fun clearTopRatedTvShowsData() { + topRatedTvShowDao.clear() + topRatedTvShowIdPageDao.clear() } - suspend fun getTopRatedTvShowIds(): List { - return topRatedTvShowIdDao.getIds() + suspend fun getTopRatedTvShowsLastPage(): Int? { + return topRatedTvShowIdPageDao.getPages().lastOrNull() } - suspend fun getNowPlayingTvShowIds(): List { - return nowPlayingTvShowIdDao.getIds() + suspend fun getNowPlayingTvShowsLastPage(): Int? { + return nowPlayingTvShowIdPageDao.getPages().lastOrNull() + } + + fun getTopRatedTvShowsWithGenresPagingSource(): + PagingSource { + return topRatedTvShowDao.getPagingSource() + } + + fun getNowPlayingTvShowsWithGenresPagingSource(): + PagingSource { + return nowPlayingTvShowDao.getPagingSource() + } + + suspend fun doTvShowGenresExist(): Boolean { + return tvShowGenreDao.doTvShowGenresExist() } suspend fun insertGenres(genres: List) { @@ -45,15 +66,19 @@ class TvShowLocalDataSource @Inject constructor( tvShowGenreCrossRefDao.insert(tvShowGenreCrossRefEntity) } - suspend fun insertTvShows(tvShowList: List) { - tvShowDao.insert(tvShowList) + suspend fun insertTopRatedTvShows(tvShowList: List) { + topRatedTvShowDao.insert(tvShowList) + } + + suspend fun insertNowPlayingTvShows(tvShowList: List) { + nowPlayingTvShowDao.insert(tvShowList) } - suspend fun insertTopRatedTvShowIds(topRatedTvShowIds: List) { - topRatedTvShowIdDao.insert(topRatedTvShowIds) + suspend fun insertTopRatedTvShowIds(topRatedTvShowIds: List) { + topRatedTvShowIdPageDao.insert(topRatedTvShowIds) } - suspend fun insertNowPlayingTvShowIds(nowPlayingTvShowIds: List) { - nowPlayingTvShowIdDao.insert(nowPlayingTvShowIds) + suspend fun insertNowPlayingTvShowIds(nowPlayingTvShowIds: List) { + nowPlayingTvShowIdPageDao.insert(nowPlayingTvShowIds) } } diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/api/MovieService.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/api/MovieService.kt index 8f4e90f..69a9e00 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/remote/api/MovieService.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/api/MovieService.kt @@ -18,7 +18,8 @@ interface MovieService { suspend fun fetchMovieGenres(): MovieGenreResponseModel @GET(NOW_PLAYING) - suspend fun fetchNowPlayingMovies(): NowPlayingMovieResponseModel + suspend fun fetchNowPlayingMovies(@Query(QUERY_PAGE) page: Int = 1): + NowPlayingMovieResponseModel @GET(DETAIL) suspend fun fetchMovieDetail(@Path(PATH_MOVIE_ID) id: Long): MovieDetailResponseModel diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/api/TvShowService.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/api/TvShowService.kt index 9cb343a..0426b67 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/remote/api/TvShowService.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/api/TvShowService.kt @@ -7,17 +7,19 @@ import com.adesso.movee.data.remote.model.tv.TvShowDetailResponseModel import com.adesso.movee.data.remote.model.tv.TvShowGenreResponseModel import retrofit2.http.GET import retrofit2.http.Path +import retrofit2.http.Query interface TvShowService { @GET(TOP_RATED) - suspend fun fetchTopRatedTvShows(): TopRatedTvShowResponseModel + suspend fun fetchTopRatedTvShows(@Query(QUERY_PAGE) page: Int = 1): TopRatedTvShowResponseModel @GET(GENRE) suspend fun fetchTvShowGenres(): TvShowGenreResponseModel @GET(ON_THE_AIR) - suspend fun fetchNowPlayingTvShows(): NowPlayingTvShowResponseModel + suspend fun fetchNowPlayingTvShows(@Query(QUERY_PAGE) page: Int = 1): + NowPlayingTvShowResponseModel @GET(DETAIL) suspend fun fetchTvShowDetail(@Path(PATH_TV_ID) id: Long): TvShowDetailResponseModel @@ -26,6 +28,7 @@ interface TvShowService { suspend fun fetchCredits(@Path(PATH_TV_ID) id: Long): TvShowCreditsResponseModel companion object { + const val QUERY_PAGE = "page" const val TOP_RATED = "tv/top_rated" const val GENRE = "genre/tv/list" const val ON_THE_AIR = "tv/on_the_air" diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/MovieRemoteDataSource.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/MovieRemoteDataSource.kt index c714415..e048e8b 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/MovieRemoteDataSource.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/MovieRemoteDataSource.kt @@ -21,8 +21,8 @@ class MovieRemoteDataSource @Inject constructor( service.fetchMovieGenres() } - suspend fun fetchNowPlayingMovies(): NowPlayingMovieResponseModel = invoke { - service.fetchNowPlayingMovies() + suspend fun fetchNowPlayingMovies(page: Int): NowPlayingMovieResponseModel = invoke { + service.fetchNowPlayingMovies(page) } suspend fun fetchMovieDetail(id: Long): MovieDetailResponseModel = invoke { diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/TvShowRemoteDataSource.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/TvShowRemoteDataSource.kt index 9c86bf8..5c58421 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/TvShowRemoteDataSource.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/datasource/TvShowRemoteDataSource.kt @@ -13,16 +13,16 @@ class TvShowRemoteDataSource @Inject constructor( private val service: TvShowService ) : BaseRemoteDataSource() { - suspend fun fetchTopRatedTvShows(): TopRatedTvShowResponseModel = invoke { - service.fetchTopRatedTvShows() + suspend fun fetchTopRatedTvShows(page: Int): TopRatedTvShowResponseModel = invoke { + service.fetchTopRatedTvShows(page) } suspend fun fetchTvShowGenres(): TvShowGenreResponseModel = invoke { service.fetchTvShowGenres() } - suspend fun fetchNowPlayingTvShows(): NowPlayingTvShowResponseModel = invoke { - service.fetchNowPlayingTvShows() + suspend fun fetchNowPlayingTvShows(page: Int): NowPlayingTvShowResponseModel = invoke { + service.fetchNowPlayingTvShows(page) } suspend fun fetchTvShowDetail(id: Long): TvShowDetailResponseModel = invoke { diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/GenericRemoteMediator.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/GenericRemoteMediator.kt new file mode 100644 index 0000000..ef75dc0 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/GenericRemoteMediator.kt @@ -0,0 +1,56 @@ +package com.adesso.movee.data.remote.mediator + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import com.adesso.movee.internal.util.Failure +import com.github.michaelbull.result.Result +import com.github.michaelbull.result.get +import com.github.michaelbull.result.onFailure +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalPagingApi::class) +class GenericRemoteMediator( + private val fetch: suspend (page: Int, clearLocalData: Boolean) + -> Result, Failure>, + private val getLastPageInLocal: suspend () -> Int?, +) : RemoteMediator() { + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val data = when (loadType) { + LoadType.REFRESH -> { + fetch.invoke(1, true).onFailure { + return MediatorResult.Error(it) + } + } + + LoadType.PREPEND -> + return MediatorResult.Success(endOfPaginationReached = true) + + LoadType.APPEND -> { + val lastPageNumber = withContext(Dispatchers.IO) { + getLastPageInLocal.invoke() + } + + if (lastPageNumber == null) { + return MediatorResult.Success(endOfPaginationReached = true) + } else { + fetch.invoke(lastPageNumber + 1, false) + .onFailure { + return MediatorResult.Error(it) + } + } + } + } + + return if (data.get().isNullOrEmpty()) { + MediatorResult.Success(endOfPaginationReached = true) + } else { + MediatorResult.Success(endOfPaginationReached = false) + } + } +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/NowPlayingMovieRemoteMediator.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/NowPlayingMovieRemoteMediator.kt new file mode 100644 index 0000000..0c6d3b3 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/mediator/NowPlayingMovieRemoteMediator.kt @@ -0,0 +1,58 @@ +package com.adesso.movee.data.remote.mediator + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import com.adesso.movee.data.local.database.entity.NowPlayingMovieEntity +import com.adesso.movee.data.local.database.entity.NowPlayingMovieWithGenres +import com.adesso.movee.internal.util.Failure +import com.github.michaelbull.result.Result +import com.github.michaelbull.result.get +import com.github.michaelbull.result.onFailure +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalPagingApi::class) +class NowPlayingMovieRemoteMediator( + private val fetchNowPlayingMovies: suspend (page: Int, clearLocalData: Boolean) + -> Result, Failure>, + private val getLastPageInLocal: suspend () -> Int?, +) : RemoteMediator() { + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val data = when (loadType) { + LoadType.REFRESH -> { + fetchNowPlayingMovies.invoke(1, true).onFailure { + return MediatorResult.Error(it) + } + } + + LoadType.PREPEND -> + return MediatorResult.Success(endOfPaginationReached = true) + + LoadType.APPEND -> { + val lastPageNumber = withContext(Dispatchers.IO) { + getLastPageInLocal.invoke() + } + + if (lastPageNumber == null) { + return MediatorResult.Success(endOfPaginationReached = true) + } else { + fetchNowPlayingMovies.invoke(lastPageNumber + 1, false) + .onFailure { + return MediatorResult.Error(it) + } + } + } + } + + return if (data.get().isNullOrEmpty()) { + MediatorResult.Success(endOfPaginationReached = true) + } else { + MediatorResult.Success(endOfPaginationReached = false) + } + } +} diff --git a/app/src/main/kotlin/com/adesso/movee/data/remote/model/tv/TvShowResponseModel.kt b/app/src/main/kotlin/com/adesso/movee/data/remote/model/tv/TvShowResponseModel.kt index 4094419..e48feb5 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/remote/model/tv/TvShowResponseModel.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/remote/model/tv/TvShowResponseModel.kt @@ -1,6 +1,7 @@ package com.adesso.movee.data.remote.model.tv -import com.adesso.movee.data.local.database.entity.TvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowEntity import com.adesso.movee.data.local.database.entity.TvShowGenreCrossRefEntity import com.adesso.movee.data.remote.BaseResponseModel import com.adesso.movee.internal.util.Image @@ -19,7 +20,19 @@ data class TvShowResponseModel( @Json(name = "first_air_date") val releaseDate: Date? ) : BaseResponseModel() { - fun toEntity() = TvShowEntity( + fun toNowPlayingEntity() = NowPlayingTvShowEntity( + id = id, + title = title, + overview = overview, + genreIds = genreIds, + posterPath = posterPath, + backdropPath = backdropPath, + popularity = popularity, + average = average, + releaseDate = releaseDate + ) + + fun toTopRatedEntity() = TopRatedTvShowEntity( id = id, title = title, overview = overview, diff --git a/app/src/main/kotlin/com/adesso/movee/data/repository/MovieRepository.kt b/app/src/main/kotlin/com/adesso/movee/data/repository/MovieRepository.kt index 20ffbdc..4cdcae3 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/repository/MovieRepository.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/repository/MovieRepository.kt @@ -6,18 +6,21 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.map import com.adesso.movee.data.local.database.entity.MovieGenreEntity +import com.adesso.movee.data.local.database.entity.NowPlayingMovieEntity import com.adesso.movee.data.local.database.entity.NowPlayingMovieIdPageEntity import com.adesso.movee.data.local.database.entity.PopularMovieEntity import com.adesso.movee.data.local.database.entity.PopularMovieIdPageEntity import com.adesso.movee.data.local.datasource.MovieLocalDataSource import com.adesso.movee.data.remote.datasource.MovieRemoteDataSource +import com.adesso.movee.data.remote.mediator.NowPlayingMovieRemoteMediator import com.adesso.movee.data.remote.mediator.PopularMovieRemoteMediator +import com.adesso.movee.data.remote.model.movie.NowPlayingMovieResponseModel import com.adesso.movee.data.remote.model.movie.PopularMovieResponseModel import com.adesso.movee.internal.util.Failure import com.adesso.movee.uimodel.MovieUiModel import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result -import com.github.michaelbull.result.toResultOr import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.Dispatchers @@ -39,7 +42,7 @@ class MovieRepository @Inject constructor( config = PagingConfig(pageSize = 20), remoteMediator = PopularMovieRemoteMediator( fetchPopularMovies = ::fetchPopularMovies, - getLastPageInLocal = localDataSource::getLastPageInDataSource + getLastPageInLocal = localDataSource::getPopularMoviesLastPage ), pagingSourceFactory = { localDataSource.getPopularMoviesWithGenresPagingSource() } ).flow.map { pagingData -> @@ -49,6 +52,22 @@ class MovieRepository @Inject constructor( } } + @OptIn(ExperimentalPagingApi::class) + fun getNowPlayingMoviesPagingFlow(): Flow> { + return Pager( + config = PagingConfig(pageSize = 20), + remoteMediator = NowPlayingMovieRemoteMediator( + fetchNowPlayingMovies = ::fetchNowPlayingMovies, + getLastPageInLocal = localDataSource::getMovieNowPlayingLastPage + ), + pagingSourceFactory = { localDataSource.getNowPlayingMoviesWithGenresPagingSource() } + ).flow.map { pagingData -> + pagingData.map { + it.toUiModel() + } + } + } + private suspend fun fetchPopularMovies(page: Int, clearLocalData: Boolean): Result, Failure> { return try { @@ -86,7 +105,7 @@ class MovieRepository @Inject constructor( PopularMovieIdPageEntity(it.id, page) } ) - }.toResultOr { Failure.IoError } + }.run { Ok(this) } } } catch (failure: Failure) { Err(failure) @@ -109,38 +128,48 @@ class MovieRepository @Inject constructor( .also { localDataSource.insertGenres(it) } } - suspend fun fetchNowPlayingMovies(): List = coroutineScope { - val deferredNowPlayingMovieResponse = async { remoteDataSource.fetchNowPlayingMovies() } - checkMovieGenres() - - deferredNowPlayingMovieResponse.await() - .movieList - .map { movieModel -> - movieModel.genreIds.map { - localDataSource.insertMovieGenreCrossRef( - movieModel.toMovieGenreCrossRefEntity(it) - ) - } - movieModel.toNowPlayingMovieEntity() - } - .also { movieEntityList -> - localDataSource.insertNowPlayingMovies(movieEntityList) - localDataSource.insertNowPlayingMovieIds( - movieEntityList.map { - NowPlayingMovieIdPageEntity( - it.id, 1 - ) + private suspend fun fetchNowPlayingMovies(page: Int, clearLocalData: Boolean): + Result, Failure> { + return try { + coroutineScope { + val response: NowPlayingMovieResponseModel + + try { + val deferredNowPlayingResponse = + async { remoteDataSource.fetchNowPlayingMovies(page) } + checkMovieGenres() + + response = deferredNowPlayingResponse.await() + } catch (failure: Failure) { + throw failure } - ) - } - val movieIds = localDataSource.getNowPlayingMovieIds() - localDataSource - .getNowPlayingMoviesWithGenres(movieIds) - .map { movieWithGenres -> - movieWithGenres.toUiModel() + if (clearLocalData) { + withContext(Dispatchers.IO) { localDataSource.clearNowPlayingMovieData() } + } + + response.movieList + .map { movieModel -> + movieModel.genreIds.map { + localDataSource.insertMovieGenreCrossRef( + movieModel.toMovieGenreCrossRefEntity(it) + ) + } + movieModel.toNowPlayingMovieEntity() + } + .also { movieEntityList -> + localDataSource.insertNowPlayingMovies(movieEntityList) + localDataSource.insertNowPlayingMovieIds( + movieEntityList.map { + NowPlayingMovieIdPageEntity(it.id, page) + } + ) + }.run { Ok(this) } + } + } catch (failure: Failure) { + Err(failure) } - } + } suspend fun fetchMovieDetail(id: Long) = remoteDataSource.fetchMovieDetail(id).toUiModel() diff --git a/app/src/main/kotlin/com/adesso/movee/data/repository/TvShowRepository.kt b/app/src/main/kotlin/com/adesso/movee/data/repository/TvShowRepository.kt index 52f221b..92cec55 100644 --- a/app/src/main/kotlin/com/adesso/movee/data/repository/TvShowRepository.kt +++ b/app/src/main/kotlin/com/adesso/movee/data/repository/TvShowRepository.kt @@ -1,15 +1,33 @@ package com.adesso.movee.data.repository -import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdEntity -import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdEntity +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.map +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowEntity +import com.adesso.movee.data.local.database.entity.NowPlayingTvShowIdPageEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowEntity +import com.adesso.movee.data.local.database.entity.TopRatedTvShowIdPageEntity import com.adesso.movee.data.local.database.entity.TvShowGenreEntity import com.adesso.movee.data.local.datasource.TvShowLocalDataSource import com.adesso.movee.data.remote.datasource.TvShowRemoteDataSource +import com.adesso.movee.data.remote.mediator.GenericRemoteMediator +import com.adesso.movee.data.remote.model.tv.NowPlayingTvShowResponseModel +import com.adesso.movee.data.remote.model.tv.TopRatedTvShowResponseModel +import com.adesso.movee.internal.util.Failure import com.adesso.movee.uimodel.TvShowUiModel +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext @Singleton class TvShowRepository @Inject constructor( @@ -17,35 +35,81 @@ class TvShowRepository @Inject constructor( private val localDataSource: TvShowLocalDataSource ) { - suspend fun fetchTopRatedTvShows(): List = coroutineScope { - val deferredTopRatedTvShowResponse = async { remoteDataSource.fetchTopRatedTvShows() } - checkTvShowGenres() - - deferredTopRatedTvShowResponse.await() - .tvShowList - .map { tvShowModel -> - tvShowModel.genreIds.map { - localDataSource.insertTvShowGenreCrossRef( - tvShowModel.toTvShowGenreCrossRefEntity(it) - ) - } - tvShowModel.toEntity() + @OptIn(ExperimentalPagingApi::class) + fun getTopRatedTvShowsPagingFlow(): Flow> { + return Pager( + config = PagingConfig(pageSize = 20), + remoteMediator = GenericRemoteMediator( + fetch = ::fetchTopRatedTvShows, + getLastPageInLocal = localDataSource::getTopRatedTvShowsLastPage + ), + pagingSourceFactory = { localDataSource.getTopRatedTvShowsWithGenresPagingSource() } + ).flow.map { pagingData -> + pagingData.map { + it.toUiModel() } - .also { tvShowEntityList -> - localDataSource.insertTvShows(tvShowEntityList) - localDataSource.insertTopRatedTvShowIds( - tvShowEntityList.map { - TopRatedTvShowIdEntity(it.id) - } - ) + } + } + + @OptIn(ExperimentalPagingApi::class) + fun getNowPlayingTvShowsPagingFlow(): Flow> { + return Pager( + config = PagingConfig(pageSize = 20), + remoteMediator = GenericRemoteMediator( + fetch = ::fetchNowPlayingTvShows, + getLastPageInLocal = localDataSource::getNowPlayingTvShowsLastPage + ), + pagingSourceFactory = { localDataSource.getNowPlayingTvShowsWithGenresPagingSource() } + ).flow.map { pagingData -> + pagingData.map { + it.toUiModel() } + } + } + + private suspend fun fetchTopRatedTvShows( + page: Int, + clearLocalData: Boolean + ): Result, Failure> { + return try { + coroutineScope { + val response: TopRatedTvShowResponseModel - val tvShowIds = localDataSource.getTopRatedTvShowIds() - localDataSource - .getTvShowsWithGenres(tvShowIds) - .map { tvShowWithGenres -> - tvShowWithGenres.toUiModel() + try { + val deferredTopRatedTvShowResponse = + async { remoteDataSource.fetchTopRatedTvShows(page) } + checkTvShowGenres() + + response = deferredTopRatedTvShowResponse.await() + } catch (failure: Failure) { + throw failure + } + + if (clearLocalData) { + withContext(Dispatchers.IO) { localDataSource.clearTopRatedTvShowsData() } + } + + response.tvShowList + .map { tvShowModel -> + tvShowModel.genreIds.map { + localDataSource.insertTvShowGenreCrossRef( + tvShowModel.toTvShowGenreCrossRefEntity(it) + ) + } + tvShowModel.toTopRatedEntity() + } + .also { tvShowEntityList -> + localDataSource.insertTopRatedTvShows(tvShowEntityList) + localDataSource.insertTopRatedTvShowIds( + tvShowEntityList.map { + TopRatedTvShowIdPageEntity(it.id, page) + } + ) + }.run { Ok(this) } } + } catch (failure: Failure) { + Err(failure) + } } private suspend fun checkTvShowGenres() { @@ -64,37 +128,50 @@ class TvShowRepository @Inject constructor( .also { localDataSource.insertGenres(it) } } - suspend fun fetchNowPlayingTvShows(): List = coroutineScope { - val deferredNowPlayingTvShowResponse = async { remoteDataSource.fetchNowPlayingTvShows() } - checkTvShowGenres() - - deferredNowPlayingTvShowResponse.await() - .tvShowList - .map { tvShowModel -> - tvShowModel.genreIds.map { - localDataSource.insertTvShowGenreCrossRef( - tvShowModel.toTvShowGenreCrossRefEntity(it) - ) + private suspend fun fetchNowPlayingTvShows( + page: Int, + clearLocalData: Boolean + ): Result, Failure> { + return try { + coroutineScope { + val response: NowPlayingTvShowResponseModel + + try { + val deferredNowPlayingTvShowResponse = + async { remoteDataSource.fetchNowPlayingTvShows(page) } + checkTvShowGenres() + + response = deferredNowPlayingTvShowResponse.await() + } catch (failure: Failure) { + throw failure + } + + if (clearLocalData) { + withContext(Dispatchers.IO) { localDataSource.clearNowPlayingTvShowsData() } } - tvShowModel.toEntity() - } - .also { tvShowEntityList -> - localDataSource.insertTvShows(tvShowEntityList) - localDataSource.insertNowPlayingTvShowIds( - tvShowEntityList.map { - NowPlayingTvShowIdEntity( - it.id - ) - } - ) - } - val tvShowIds = localDataSource.getNowPlayingTvShowIds() - localDataSource - .getTvShowsWithGenres(tvShowIds) - .map { tvShowWithGenres -> - tvShowWithGenres.toUiModel() + response + .tvShowList + .map { tvShowModel -> + tvShowModel.genreIds.map { + localDataSource.insertTvShowGenreCrossRef( + tvShowModel.toTvShowGenreCrossRefEntity(it) + ) + } + tvShowModel.toNowPlayingEntity() + } + .also { tvShowEntityList -> + localDataSource.insertNowPlayingTvShows(tvShowEntityList) + localDataSource.insertNowPlayingTvShowIds( + tvShowEntityList.map { + NowPlayingTvShowIdPageEntity(it.id, page) + } + ) + }.run { Ok(this) } } + } catch (failure: Failure) { + Err(failure) + } } suspend fun fetchTvShowDetail(id: Long) = remoteDataSource.fetchTvShowDetail(id).toUiModel() diff --git a/app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingTvShowsUseCase.kt b/app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingTvShowsUseCase.kt deleted file mode 100644 index 4b3d62c..0000000 --- a/app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingTvShowsUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.adesso.movee.domain - -import com.adesso.movee.data.repository.TvShowRepository -import com.adesso.movee.internal.util.UseCase -import com.adesso.movee.uimodel.TvShowUiModel -import javax.inject.Inject - -class FetchNowPlayingTvShowsUseCase @Inject constructor( - private val repository: TvShowRepository -) : UseCase, UseCase.None>() { - - override suspend fun buildUseCase(params: None) = repository.fetchNowPlayingTvShows() -} diff --git a/app/src/main/kotlin/com/adesso/movee/domain/FetchTopRatedTvShowsUseCase.kt b/app/src/main/kotlin/com/adesso/movee/domain/FetchTopRatedTvShowsUseCase.kt deleted file mode 100644 index 739ddb9..0000000 --- a/app/src/main/kotlin/com/adesso/movee/domain/FetchTopRatedTvShowsUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.adesso.movee.domain - -import com.adesso.movee.data.repository.TvShowRepository -import com.adesso.movee.internal.util.UseCase -import com.adesso.movee.uimodel.TvShowUiModel -import javax.inject.Inject - -class FetchTopRatedTvShowsUseCase @Inject constructor( - private val repository: TvShowRepository -) : UseCase, UseCase.None>() { - - override suspend fun buildUseCase(params: None) = repository.fetchTopRatedTvShows() -} diff --git a/app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingMoviesUseCase.kt b/app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingMoviesPagingFlowUseCase.kt similarity index 58% rename from app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingMoviesUseCase.kt rename to app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingMoviesPagingFlowUseCase.kt index b253334..0f253e6 100644 --- a/app/src/main/kotlin/com/adesso/movee/domain/FetchNowPlayingMoviesUseCase.kt +++ b/app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingMoviesPagingFlowUseCase.kt @@ -1,13 +1,15 @@ package com.adesso.movee.domain +import androidx.paging.PagingData import com.adesso.movee.data.repository.MovieRepository import com.adesso.movee.internal.util.UseCase import com.adesso.movee.uimodel.MovieUiModel import javax.inject.Inject +import kotlinx.coroutines.flow.Flow -class FetchNowPlayingMoviesUseCase @Inject constructor( +class GetNowPlayingMoviesPagingFlowUseCase @Inject constructor( private val repository: MovieRepository -) : UseCase, UseCase.None>() { +) : UseCase>, UseCase.None>() { - override suspend fun buildUseCase(params: None) = repository.fetchNowPlayingMovies() + override suspend fun buildUseCase(params: None) = repository.getNowPlayingMoviesPagingFlow() } diff --git a/app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingTvShowsPagingFlowUseCase.kt b/app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingTvShowsPagingFlowUseCase.kt new file mode 100644 index 0000000..95acd08 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/domain/GetNowPlayingTvShowsPagingFlowUseCase.kt @@ -0,0 +1,17 @@ +package com.adesso.movee.domain + +import androidx.paging.PagingData +import com.adesso.movee.data.repository.TvShowRepository +import com.adesso.movee.internal.util.UseCase +import com.adesso.movee.uimodel.TvShowUiModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +class GetNowPlayingTvShowsPagingFlowUseCase @Inject constructor( + private val tvShowRepository: TvShowRepository +) : UseCase>, UseCase.None>() { + + override suspend fun buildUseCase(params: None): Flow> { + return tvShowRepository.getNowPlayingTvShowsPagingFlow() + } +} diff --git a/app/src/main/kotlin/com/adesso/movee/domain/GetTopRatedTvShowsPagingFlowUseCase.kt b/app/src/main/kotlin/com/adesso/movee/domain/GetTopRatedTvShowsPagingFlowUseCase.kt new file mode 100644 index 0000000..1590ef9 --- /dev/null +++ b/app/src/main/kotlin/com/adesso/movee/domain/GetTopRatedTvShowsPagingFlowUseCase.kt @@ -0,0 +1,17 @@ +package com.adesso.movee.domain + +import androidx.paging.PagingData +import com.adesso.movee.data.repository.TvShowRepository +import com.adesso.movee.internal.util.UseCase +import com.adesso.movee.uimodel.TvShowUiModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +class GetTopRatedTvShowsPagingFlowUseCase @Inject constructor( + private val tvShowRepository: TvShowRepository +) : UseCase>, UseCase.None>() { + + override suspend fun buildUseCase(params: None): Flow> { + return tvShowRepository.getTopRatedTvShowsPagingFlow() + } +} diff --git a/app/src/main/kotlin/com/adesso/movee/internal/databinding/ViewBindingAdapters.kt b/app/src/main/kotlin/com/adesso/movee/internal/databinding/ViewBindingAdapters.kt index 0e346bc..418054e 100644 --- a/app/src/main/kotlin/com/adesso/movee/internal/databinding/ViewBindingAdapters.kt +++ b/app/src/main/kotlin/com/adesso/movee/internal/databinding/ViewBindingAdapters.kt @@ -68,13 +68,6 @@ fun submitList(view: RecyclerView, data: PagingData) { adapter?.submitData(ViewTreeLifecycleOwner.get(view)!!.lifecycle, data) } -@BindingAdapter("adapter") -fun setAdapter(view: RecyclerView, adapter: BaseListAdapter?) { - adapter?.let { - view.adapter = it - } -} - @BindingAdapter("spaceItemDecoration") fun addSpaceItemDecoration(view: RecyclerView, @Dimension space: Float) { val spaceItemDecoration = GridLayoutSpaceItemDecoration(space.toInt()) diff --git a/app/src/main/kotlin/com/adesso/movee/internal/injection/module/DatabaseModule.kt b/app/src/main/kotlin/com/adesso/movee/internal/injection/module/DatabaseModule.kt index d0bffdb..8eeae86 100644 --- a/app/src/main/kotlin/com/adesso/movee/internal/injection/module/DatabaseModule.kt +++ b/app/src/main/kotlin/com/adesso/movee/internal/injection/module/DatabaseModule.kt @@ -3,15 +3,16 @@ package com.adesso.movee.internal.injection.module import android.app.Application import androidx.room.Room import com.adesso.movee.data.local.database.MainDatabase -import com.adesso.movee.data.local.database.dao.PopularMovieDao import com.adesso.movee.data.local.database.dao.MovieGenreCrossRefDao import com.adesso.movee.data.local.database.dao.MovieGenreDao import com.adesso.movee.data.local.database.dao.NowPlayingMovieDao import com.adesso.movee.data.local.database.dao.NowPlayingMovieIdPageDao -import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdDao +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowDao +import com.adesso.movee.data.local.database.dao.NowPlayingTvShowIdPageDao +import com.adesso.movee.data.local.database.dao.PopularMovieDao import com.adesso.movee.data.local.database.dao.PopularMovieIdPageDao -import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdDao -import com.adesso.movee.data.local.database.dao.TvShowDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowDao +import com.adesso.movee.data.local.database.dao.TopRatedTvShowIdPageDao import com.adesso.movee.data.local.database.dao.TvShowGenreCrossRefDao import com.adesso.movee.data.local.database.dao.TvShowGenreDao import dagger.Module @@ -72,20 +73,26 @@ object DatabaseModule { @Provides @Singleton - fun provideTvShowDao(mainDatabase: MainDatabase): TvShowDao { - return mainDatabase.tvShowDao() + fun provideNowPlayingTvShowDao(mainDatabase: MainDatabase): NowPlayingTvShowDao { + return mainDatabase.nowPlayingTvShowDao() + } + + @Provides + @Singleton + fun provideTopRatedTvShowDao(mainDatabase: MainDatabase): TopRatedTvShowDao { + return mainDatabase.topRatedTvShowDao() } @Provides @Singleton - fun provideTopRatedTvShowIdDao(mainDatabase: MainDatabase): TopRatedTvShowIdDao { - return mainDatabase.topRatedTvShowIdDao() + fun provideTopRatedTvShowIdPageDao(mainDatabase: MainDatabase): TopRatedTvShowIdPageDao { + return mainDatabase.topRatedTvShowIdPageDao() } @Provides @Singleton - fun provideNowPlayingTvShowIdDao(mainDatabase: MainDatabase): NowPlayingTvShowIdDao { - return mainDatabase.nowPlayingTvShowIdDao() + fun provideNowPlayingTvShowIdPageDao(mainDatabase: MainDatabase): NowPlayingTvShowIdPageDao { + return mainDatabase.nowPlayingTvShowIdPageDao() } @Provides diff --git a/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieFragment.kt b/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieFragment.kt index 7b8f67d..5a912fc 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieFragment.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieFragment.kt @@ -22,7 +22,7 @@ class MovieFragment : override fun initialize() { super.initialize() - binder.popularMovieAdapter = PopularMovieListAdapter(popularMovieCallback = this) + binder.popularMovieAdapter = PopularMoviePagingAdapter(popularMovieCallback = this) binder.layoutShowHeader.nowPlayingShowCallback = this binder.layoutShowHeader.appBarShow.addAppBarStateChangeListener { _, state -> viewModel.appbarStateChanged(state) @@ -34,7 +34,7 @@ class MovieFragment : viewModel.onPopularMovieClick(movie) } - override fun onNowPlayingShowClick(show: ShowUiModel) { + override fun onShowClick(show: ShowUiModel) { viewModel.onNowPlayingShowClick(show) } @@ -42,8 +42,10 @@ class MovieFragment : collectFlow(viewModel.shouldRefreshPaging) { if (it) { binder.popularMovieAdapter?.refresh() + binder.layoutShowHeader.nowPlayingShowView.nowPlayingShowAdapter.refresh() requireContext().toast(getString(R.string.common_paging_list_refreshed_message)) binder.recyclerViewPopularMovies.scrollToPosition(0) + binder.layoutShowHeader.nowPlayingShowView.setCurrentItem(0) } } } diff --git a/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieViewModel.kt b/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieViewModel.kt index 418c320..ea044ac 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieViewModel.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/movie/MovieViewModel.kt @@ -8,7 +8,7 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import com.adesso.movee.R import com.adesso.movee.base.BaseAndroidViewModel -import com.adesso.movee.domain.FetchNowPlayingMoviesUseCase +import com.adesso.movee.domain.GetNowPlayingMoviesPagingFlowUseCase import com.adesso.movee.domain.GetPopularMoviesPagingFlowUseCase import com.adesso.movee.domain.ShouldRefreshPagingUseCase import com.adesso.movee.internal.util.AppBarStateChangeListener @@ -32,7 +32,7 @@ import kotlinx.coroutines.launch @HiltViewModel class MovieViewModel @Inject constructor( private val getPopularMoviesPagingFlowUseCase: GetPopularMoviesPagingFlowUseCase, - private val fetchNowPlayingMoviesUseCase: FetchNowPlayingMoviesUseCase, + private val getNowPlayingMoviesPagingFlowUseCase: GetNowPlayingMoviesPagingFlowUseCase, private val shouldRefreshPagingUseCase: ShouldRefreshPagingUseCase, application: Application ) : BaseAndroidViewModel(application) { @@ -40,17 +40,19 @@ class MovieViewModel @Inject constructor( private val _popularMovies = MutableStateFlow>(PagingData.empty()) private val _toolbarTitle = MutableLiveData() private val _toolbarSubtitle = MutableLiveData(getString(R.string.movie_message_popular)) - private val _nowPlayingMovies = MutableLiveData>() + private val _nowPlayingMovies = MutableLiveData>(PagingData.empty()) val popularMovies = _popularMovies.asStateFlow() + + @Suppress("UNCHECKED_CAST") val showHeader = TripleCombinedLiveData( _toolbarTitle, _toolbarSubtitle, _nowPlayingMovies - ) { title, subtitle, nowPlayingShows -> + ) { title, subtitle, nowPlayingMovies -> ShowHeaderUiModel( title, subtitle, - nowPlayingShows + nowPlayingMovies as PagingData? ) } @@ -63,20 +65,16 @@ class MovieViewModel @Inject constructor( private fun fetchNowPlayingMovies() { viewModelScope.launch { - val nowPlayingMoviesResult = fetchNowPlayingMoviesUseCase.run(UseCase.None) + val nowPlayingMoviesResult = getNowPlayingMoviesPagingFlowUseCase.run(UseCase.None) runOnViewModelScope { nowPlayingMoviesResult - .onSuccess(::postNowPlayingMovieList) + .onSuccess(::postNowPlayingMoviesPagedData) .onFailure(::handleFailure) } } } - private fun postNowPlayingMovieList(movies: List) { - _nowPlayingMovies.value = movies - } - private fun fetchPopularMovies() { viewModelScope.launch { val popularMoviesResult = getPopularMoviesPagingFlowUseCase.run(UseCase.None) @@ -97,6 +95,14 @@ class MovieViewModel @Inject constructor( } } + private fun postNowPlayingMoviesPagedData(pagingFlow: Flow>) { + viewModelScope.launch { + pagingFlow.cachedIn(viewModelScope).collect { + _nowPlayingMovies.value = it + } + } + } + private fun postToolbarTitle(@StringRes titleRes: Int) { _toolbarTitle.value = getString(titleRes) } diff --git a/app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMovieListAdapter.kt b/app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMoviePagingAdapter.kt similarity index 96% rename from app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMovieListAdapter.kt rename to app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMoviePagingAdapter.kt index d8f45bf..b99d795 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMovieListAdapter.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/movie/PopularMoviePagingAdapter.kt @@ -11,7 +11,7 @@ interface PopularMovieCallback { fun onPopularMovieClick(movie: MovieUiModel) } -class PopularMovieListAdapter( +class PopularMoviePagingAdapter( private val popularMovieCallback: PopularMovieCallback, ) : BasePagingAdapter() { diff --git a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowListAdapter.kt b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowPagingAdapter.kt similarity index 66% rename from app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowListAdapter.kt rename to app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowPagingAdapter.kt index 339b40a..6853e64 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowListAdapter.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TopRatedTvShowPagingAdapter.kt @@ -1,7 +1,7 @@ package com.adesso.movee.scene.tvshow import com.adesso.movee.R -import com.adesso.movee.base.BaseListAdapter +import com.adesso.movee.base.BasePagingAdapter import com.adesso.movee.databinding.ItemTopRatedTvShowBinding import com.adesso.movee.internal.extension.executeAfter import com.adesso.movee.uimodel.TvShowUiModel @@ -11,8 +11,12 @@ interface TopRatedTvShowCallback { fun onTopRatedTvShowClick(tvShow: TvShowUiModel) } -class TopRatedTvShowListAdapter(private val topRatedTvShowCallback: TopRatedTvShowCallback) : - BaseListAdapter() { +class TopRatedTvShowPagingAdapter(private val topRatedTvShowCallback: TopRatedTvShowCallback) : + BasePagingAdapter() { + + init { + stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY + } override val layoutRes: Int get() = R.layout.item_top_rated_tv_show diff --git a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowFragment.kt b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowFragment.kt index 42bc3e1..8228049 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowFragment.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowFragment.kt @@ -3,6 +3,8 @@ package com.adesso.movee.scene.tvshow import com.adesso.movee.R import com.adesso.movee.base.BaseFragment import com.adesso.movee.databinding.FragmentTvShowBinding +import com.adesso.movee.internal.extension.collectFlow +import com.adesso.movee.internal.extension.toast import com.adesso.movee.internal.util.addAppBarStateChangeListener import com.adesso.movee.uimodel.ShowUiModel import com.adesso.movee.uimodel.TvShowUiModel @@ -20,18 +22,31 @@ class TvShowFragment : override fun initialize() { super.initialize() - binder.topRatedTvShowAdapter = TopRatedTvShowListAdapter(topRatedTvShowCallback = this) + binder.topRatedTvShowAdapter = TopRatedTvShowPagingAdapter(topRatedTvShowCallback = this) binder.layoutShowHeader.nowPlayingShowCallback = this binder.layoutShowHeader.appBarShow.addAppBarStateChangeListener { _, state -> viewModel.onAppBarStateChanged(state) } + setShouldRefreshPagingListener() } override fun onTopRatedTvShowClick(tvShow: TvShowUiModel) { viewModel.onTopRatedTvShowClick(tvShow) } - override fun onNowPlayingShowClick(show: ShowUiModel) { + override fun onShowClick(show: ShowUiModel) { viewModel.onNowPlayingShowClick(show) } + + private fun setShouldRefreshPagingListener() { + collectFlow(viewModel.shouldRefreshPaging) { + if (it) { + binder.topRatedTvShowAdapter?.refresh() + binder.layoutShowHeader.nowPlayingShowView.nowPlayingShowAdapter.refresh() + requireContext().toast(getString(R.string.common_paging_list_refreshed_message)) + binder.recyclerViewTopRatedTvShow.scrollToPosition(0) + binder.layoutShowHeader.nowPlayingShowView.setCurrentItem(0) + } + } + } } diff --git a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowViewModel.kt b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowViewModel.kt index e5ea1f7..ea25a9a 100644 --- a/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowViewModel.kt +++ b/app/src/main/kotlin/com/adesso/movee/scene/tvshow/TvShowViewModel.kt @@ -1,13 +1,15 @@ package com.adesso.movee.scene.tvshow import android.app.Application -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.adesso.movee.R import com.adesso.movee.base.BaseAndroidViewModel -import com.adesso.movee.domain.FetchNowPlayingTvShowsUseCase -import com.adesso.movee.domain.FetchTopRatedTvShowsUseCase +import com.adesso.movee.domain.GetNowPlayingTvShowsPagingFlowUseCase +import com.adesso.movee.domain.GetTopRatedTvShowsPagingFlowUseCase +import com.adesso.movee.domain.ShouldRefreshPagingUseCase import com.adesso.movee.internal.util.AppBarStateChangeListener import com.adesso.movee.internal.util.AppBarStateChangeListener.State.COLLAPSED import com.adesso.movee.internal.util.AppBarStateChangeListener.State.EXPANDED @@ -20,21 +22,27 @@ import com.adesso.movee.uimodel.TvShowUiModel import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onSuccess import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch @HiltViewModel class TvShowViewModel @Inject constructor( - private val fetchTopRatedTvShowsUseCase: FetchTopRatedTvShowsUseCase, - private val fetchNowPlayingTvShowsUseCase: FetchNowPlayingTvShowsUseCase, + private val shouldRefreshPagingUseCase: ShouldRefreshPagingUseCase, + private val getNowPlayingTvShowsPagingFlowUseCase: GetNowPlayingTvShowsPagingFlowUseCase, + private val getTopRatedTvShowsPagingFlowUseCase: GetTopRatedTvShowsPagingFlowUseCase, application: Application ) : BaseAndroidViewModel(application) { - private val _topRatedTvShows = MutableLiveData>() + private val _topRatedTvShows = MutableStateFlow>(PagingData.empty()) private val _toolbarTitle = MutableLiveData() private val _toolbarSubtitle = MutableLiveData(getString(R.string.tv_show_message_top_rated)) - private val _nowPlayingTvShows = MutableLiveData>() - val topRatedTvShows: LiveData> get() = _topRatedTvShows + private val _nowPlayingTvShows = MutableLiveData>(PagingData.empty()) + val topRatedTvShows = _topRatedTvShows.asStateFlow() + + @Suppress("UNCHECKED_CAST") val showHeader = TripleCombinedLiveData( _toolbarTitle, _toolbarSubtitle, @@ -43,10 +51,12 @@ class TvShowViewModel @Inject constructor( ShowHeaderUiModel( title, subtitle, - nowPlayingShows + nowPlayingShows as PagingData? ) } + val shouldRefreshPaging = shouldRefreshPagingUseCase.execute() + init { fetchTopRatedTvShows() fetchNowPlayingTvShows() @@ -54,7 +64,7 @@ class TvShowViewModel @Inject constructor( private fun fetchTopRatedTvShows() { viewModelScope.launch { - val topRatedTvShowsResult = fetchTopRatedTvShowsUseCase.run(UseCase.None) + val topRatedTvShowsResult = getTopRatedTvShowsPagingFlowUseCase.run(UseCase.None) runOnViewModelScope { topRatedTvShowsResult @@ -64,13 +74,9 @@ class TvShowViewModel @Inject constructor( } } - private fun postTopRatedTvShows(tvShows: List) { - _topRatedTvShows.value = tvShows - } - private fun fetchNowPlayingTvShows() { viewModelScope.launch { - val nowPlayingTvShowsResult = fetchNowPlayingTvShowsUseCase.run(UseCase.None) + val nowPlayingTvShowsResult = getNowPlayingTvShowsPagingFlowUseCase.run(UseCase.None) runOnViewModelScope { nowPlayingTvShowsResult @@ -80,8 +86,20 @@ class TvShowViewModel @Inject constructor( } } - private fun postNowPlayingTvShows(tvShows: List) { - _nowPlayingTvShows.value = tvShows + private fun postTopRatedTvShows(pagingFlow: Flow>) { + viewModelScope.launch { + pagingFlow.cachedIn(viewModelScope).collect { + _topRatedTvShows.value = it + } + } + } + + private fun postNowPlayingTvShows(pagingFlow: Flow>) { + viewModelScope.launch { + pagingFlow.cachedIn(viewModelScope).collect { + _nowPlayingTvShows.value = it + } + } } fun onAppBarStateChanged(state: AppBarStateChangeListener.State) { diff --git a/app/src/main/kotlin/com/adesso/movee/uimodel/ShowHeaderUiModel.kt b/app/src/main/kotlin/com/adesso/movee/uimodel/ShowHeaderUiModel.kt index c9d797a..f3175e5 100644 --- a/app/src/main/kotlin/com/adesso/movee/uimodel/ShowHeaderUiModel.kt +++ b/app/src/main/kotlin/com/adesso/movee/uimodel/ShowHeaderUiModel.kt @@ -1,7 +1,9 @@ package com.adesso.movee.uimodel +import androidx.paging.PagingData + class ShowHeaderUiModel( val title: String?, val subtitle: String?, - val nowPlayingShows: List? + val nowPlayingShows: PagingData? ) diff --git a/app/src/main/kotlin/com/adesso/movee/widget/nowplayingshow/NowPlayingShowView.kt b/app/src/main/kotlin/com/adesso/movee/widget/nowplayingshow/NowPlayingShowView.kt index c891bce..35bb686 100644 --- a/app/src/main/kotlin/com/adesso/movee/widget/nowplayingshow/NowPlayingShowView.kt +++ b/app/src/main/kotlin/com/adesso/movee/widget/nowplayingshow/NowPlayingShowView.kt @@ -6,18 +6,26 @@ import android.view.LayoutInflater import android.widget.LinearLayout import androidx.databinding.BindingAdapter import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.lifecycle.ViewTreeLifecycleOwner +import androidx.paging.PagingData import androidx.viewpager2.widget.ViewPager2 import com.adesso.movee.R -import com.adesso.movee.base.BaseListAdapter +import com.adesso.movee.base.BasePagingAdapter +import com.adesso.movee.base.ListAdapterItem import com.adesso.movee.databinding.ItemNowPlayingShowBinding import com.adesso.movee.databinding.ViewNowPlayingShowBinding import com.adesso.movee.internal.extension.executeAfter -import com.adesso.movee.internal.extension.thisOrEmptyList import com.adesso.movee.uimodel.ShowUiModel +@Suppress("UNCHECKED_CAST") @BindingAdapter("submitList") -fun submitList(view: NowPlayingShowView, showUiModelList: List?) { - view.updateList(showUiModelList ?: emptyList()) +fun submitList(view: NowPlayingShowView, data: PagingData) { + val adapter = view.nowPlayingShowAdapter as BasePagingAdapter? + + if (ViewTreeLifecycleOwner.get(view)?.lifecycle == null) return + + adapter?.submitData(ViewTreeLifecycleOwner.get(view)!!.lifecycle, data) } @BindingAdapter("callback") @@ -36,25 +44,14 @@ class NowPlayingShowView @JvmOverloads constructor( this, true ) - private val nowPlayingShowAdapter = NowPlayingShowAdapter() - - private var showUiModelList: List? = null - set(value) { - if (value == showUiModelList) { - return - } - - field = value.thisOrEmptyList() - nowPlayingShowAdapter.submitList(showUiModelList) - binder.viewPagerNowPlayingShow.currentItem = 0 - } + val nowPlayingShowAdapter = NowPlayingShowAdapter() init { orientation = VERTICAL - with(binder.viewPagerNowPlayingShow) { + with(binder.viewPagerHeaderShow) { adapter = nowPlayingShowAdapter - offscreenPageLimit = 3 + offscreenPageLimit = 20 orientation = ViewPager2.ORIENTATION_HORIZONTAL val pageMargin = resources.getDimensionPixelSize(R.dimen.margin_now_playing_page) @@ -67,9 +64,11 @@ class NowPlayingShowView @JvmOverloads constructor( registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { - super.onPageSelected(position) + if (nowPlayingShowAdapter.itemCount == 0) return + + val showUiModel = + nowPlayingShowAdapter.getShow(position) - val showUiModel = nowPlayingShowAdapter.getShow(position) binder.executeAfter { show = showUiModel } @@ -79,10 +78,6 @@ class NowPlayingShowView @JvmOverloads constructor( } } - fun updateList(showUiModels: List) { - showUiModelList = showUiModels - } - override fun setOrientation(orientation: Int) { if (orientation != VERTICAL) { throw IllegalArgumentException("NowPlayingShowView only supports vertical orientation") @@ -93,15 +88,19 @@ class NowPlayingShowView @JvmOverloads constructor( fun setNowPlayingShowCallback(callback: NowPlayingShowCallback) { nowPlayingShowAdapter.nowPlayingShowCallback = callback } + + fun setCurrentItem(index: Int) { + binder.viewPagerHeaderShow.currentItem = index + } } interface NowPlayingShowCallback { - fun onNowPlayingShowClick(show: ShowUiModel) + fun onShowClick(show: ShowUiModel) } -private class NowPlayingShowAdapter : - BaseListAdapter() { +class NowPlayingShowAdapter : + BasePagingAdapter() { var nowPlayingShowCallback: NowPlayingShowCallback? = null @@ -114,7 +113,7 @@ private class NowPlayingShowAdapter : } } - fun getShow(position: Int): ShowUiModel { + fun getShow(position: Int): ShowUiModel? { return getItem(position) } } diff --git a/app/src/main/res/layout/fragment_movie.xml b/app/src/main/res/layout/fragment_movie.xml index 4a5b254..3577d36 100644 --- a/app/src/main/res/layout/fragment_movie.xml +++ b/app/src/main/res/layout/fragment_movie.xml @@ -12,7 +12,7 @@ + type="com.adesso.movee.scene.movie.PopularMoviePagingAdapter" /> diff --git a/app/src/main/res/layout/fragment_tv_show.xml b/app/src/main/res/layout/fragment_tv_show.xml index 75c8641..c16c5f4 100644 --- a/app/src/main/res/layout/fragment_tv_show.xml +++ b/app/src/main/res/layout/fragment_tv_show.xml @@ -12,7 +12,7 @@ + type="com.adesso.movee.scene.tvshow.TopRatedTvShowPagingAdapter" /> diff --git a/app/src/main/res/layout/item_now_playing_show.xml b/app/src/main/res/layout/item_now_playing_show.xml index 8b6f96a..633b119 100644 --- a/app/src/main/res/layout/item_now_playing_show.xml +++ b/app/src/main/res/layout/item_now_playing_show.xml @@ -20,7 +20,7 @@ android:layout_height="match_parent" android:layout_marginStart="@dimen/margin_and_offset_now_playing_page" android:layout_marginEnd="@dimen/margin_and_offset_now_playing_page" - android:onClick="@{() -> callback.onNowPlayingShowClick(show)}"> + android:onClick="@{() -> callback.onShowClick(show)}">