refactor account tabs to use paging

This commit is contained in:
Owen LeJeune
2023-06-21 16:28:07 -04:00
parent 17e3ad32f0
commit 5a9e71d0a6
40 changed files with 394 additions and 976 deletions

View File

@@ -0,0 +1,64 @@
package com.owenlejeune.tvtime.api.tmdb.api
import android.content.Context
import android.widget.Toast
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.viewmodel.ViewModelConstants
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import retrofit2.Response
fun <T: Any, S> ViewModel.createPagingFlow(
fetcher: suspend (Int) -> Response<S>,
processor: (S) -> List<T>
): Flow<PagingData<T>> {
return Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
BasePagingSource(
fetcher = fetcher,
processor = processor
)
}.flow.cachedIn(viewModelScope)
}
class BasePagingSource<T: Any, S>(
private val fetcher: suspend (Int) -> Response<S>,
private val processor: (S) -> List<T>
): PagingSource<Int, T>(), KoinComponent {
private val context: Context by inject()
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
return try {
val page = params.key ?: 1
val response = fetcher(page)
if (response.isSuccessful) {
val responseBody = response.body()
val results = responseBody?.let(processor) ?: emptyList()
LoadResult.Page(
data = results,
prevKey = if (page == 1) { null } else { page - 1},
nextKey = if (results.isEmpty()) { null } else { page + 1}
)
} else {
Toast.makeText(context, context.getString(R.string.no_result_found), Toast.LENGTH_SHORT).show()
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -9,54 +9,12 @@ interface AccountApi {
@GET("account") @GET("account")
suspend fun getAccountDetails(): Response<AccountDetails> suspend fun getAccountDetails(): Response<AccountDetails>
@GET("account/{id}/favorite/movies")
suspend fun getFavoriteMovies(
@Path("id") id: Int,
@Query("page") page: Int
): Response<FavoriteMediaResponse<FavoriteMovie>>
@GET("account/{id}/favorite/tv")
suspend fun getFavoriteTvShows(
@Path("id") id: Int,
@Query("page") page: Int
): Response<FavoriteMediaResponse<FavoriteTvSeries>>
@POST("account/{id}/favorite") @POST("account/{id}/favorite")
suspend fun markAsFavorite( suspend fun markAsFavorite(
@Path("id") id: Int, @Path("id") id: Int,
@Body body: MarkAsFavoriteBody @Body body: MarkAsFavoriteBody
): Response<StatusResponse> ): Response<StatusResponse>
@GET("account/{id}/rated/movies")
suspend fun getRatedMovies(
@Path("id") id: Int,
@Query("page") page: Int
): Response<RatedMediaResponse<RatedMovie>>
@GET("account/{id}/rated/tv")
suspend fun getRatedTvShows(
@Path("id") id: Int,
@Query("page") page: Int
): Response<RatedMediaResponse<RatedTv>>
@GET("account/{id}/rated/tv/episodes")
suspend fun getRatedTvEpisodes(
@Path("id") id: Int,
@Query("page") page: Int
): Response<RatedMediaResponse<RatedEpisode>>
@GET("account/{id}/watchlist/movies")
suspend fun getMovieWatchlist(
@Path("id") id: Int,
@Query("page") page: Int
): Response<WatchlistResponse<WatchlistMovie>>
@GET("account/{id}/watchlist/tv")
suspend fun getTvWatchlist(
@Path("id") id: Int,
@Query("page") page: Int
): Response<WatchlistResponse<WatchlistTvSeries>>
@POST("account/{id}/watchlist") @POST("account/{id}/watchlist")
suspend fun addToWatchlist( suspend fun addToWatchlist(
@Path("id") id: Int, @Path("id") id: Int,

View File

@@ -2,18 +2,8 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3
import android.util.Log import android.util.Log
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountDetails import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountDetails
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMediaResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MarkAsFavoriteBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MarkAsFavoriteBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedEpisode
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedMediaResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedTv
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import retrofit2.Response import retrofit2.Response
@@ -46,33 +36,4 @@ class AccountService: KoinComponent {
} }
} }
// TODO - replace these with account states API calls
suspend fun getFavoriteMovies(accountId: Int, page: Int = 1): Response<FavoriteMediaResponse<FavoriteMovie>> {
return accountService.getFavoriteMovies(accountId, page)
}
suspend fun getFavoriteTvShows(accountId: Int, page: Int = 1): Response<FavoriteMediaResponse<FavoriteTvSeries>> {
return accountService.getFavoriteTvShows(accountId, page)
}
suspend fun getRatedMovies(accountId: Int, page: Int = 1): Response<RatedMediaResponse<RatedMovie>> {
return accountService.getRatedMovies(accountId, page)
}
suspend fun getRatedTvShows(accountId: Int, page: Int = 1): Response<RatedMediaResponse<RatedTv>> {
return accountService.getRatedTvShows(accountId, page)
}
suspend fun getRatedTvEpisodes(accountId: Int, page: Int = 1): Response<RatedMediaResponse<RatedEpisode>> {
return accountService.getRatedTvEpisodes(accountId, page)
}
suspend fun getMovieWatchlist(accountId: Int, page: Int = 1): Response<WatchlistResponse<WatchlistMovie>> {
return accountService.getMovieWatchlist(accountId, page)
}
suspend fun getTvWatchlist(accountId: Int, page: Int = 1): Response<WatchlistResponse<WatchlistTvSeries>> {
return accountService.getTvWatchlist(accountId, page)
}
} }

View File

@@ -106,7 +106,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
} else { } else {
Log.d(TAG, "Issue getting account states: $response") Log.d(TAG, "Issue getting account states: $response")
} }
// movieService.getAccountStates(id) storedIn { accountStates[id] = it }
} }
suspend fun getReleaseDates(id: Int) { suspend fun getReleaseDates(id: Int) {
@@ -118,7 +117,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
val response = movieService.postMovieRatingAsUser(id, session.sessionId, ratingBody) val response = movieService.postMovieRatingAsUser(id, session.sessionId, ratingBody)
if (response.isSuccessful) { if (response.isSuccessful) {
Log.d(TAG, "Successfully rated") Log.d(TAG, "Successfully rated")
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
getAccountStates(id) getAccountStates(id)
} else { } else {
Log.w(TAG, "Issue posting rating") Log.w(TAG, "Issue posting rating")
@@ -130,7 +128,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
val response = movieService.deleteMovieReviewAsUser(id, session.sessionId) val response = movieService.deleteMovieReviewAsUser(id, session.sessionId)
if (response.isSuccessful) { if (response.isSuccessful) {
Log.d(TAG, "Successfully deleted rated") Log.d(TAG, "Successfully deleted rated")
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
getAccountStates(id) getAccountStates(id)
} else { } else {
Log.w(TAG, "Issue deleting rating") Log.w(TAG, "Issue deleting rating")
@@ -158,41 +155,3 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
return movieService.getUpcomingMovies(page) return movieService.getUpcomingMovies(page)
} }
} }
class SimilarMoviesSource(private val movieId: Int): PagingSource<Int, TmdbItem>(), KoinComponent {
private val service: MoviesService by inject()
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
return try {
val nextPage = params.key ?: 1
val response = service.getSimilar(movieId, nextPage)
if (response.isSuccessful) {
val responseBody = response.body()
val result = responseBody?.results ?: emptyList()
LoadResult.Page(
data = result,
prevKey = if (nextPage == 1) {
null
} else {
nextPage - 1
},
nextKey = if (result.isEmpty()) {
null
} else {
responseBody?.page?.plus(1) ?: (nextPage + 1)
}
)
} else {
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -58,41 +58,3 @@ class SearchService: KoinComponent {
} }
typealias SearchResultProvider<T> = suspend (Int) -> Response<SearchResult<T>> typealias SearchResultProvider<T> = suspend (Int) -> Response<SearchResult<T>>
class SearchPagingSource<T: Searchable>(
private val provideResults: SearchResultProvider<T>
): PagingSource<Int, T>(), KoinComponent {
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
return try {
val nextPage = params.key ?: 1
val response = provideResults(nextPage)
if (response.isSuccessful) {
val responseBody = response.body()
val result = responseBody?.results ?: emptyList()
LoadResult.Page(
data = result,
prevKey = if (nextPage == 1) {
null
} else {
nextPage - 1
},
nextKey = if (result.isEmpty()) {
null
} else {
responseBody?.page?.plus(1) ?: (nextPage + 1)
}
)
} else {
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -122,7 +122,6 @@ class TvService: KoinComponent, DetailService, HomePageService {
val response = service.postTvRatingAsUser(id, session.sessionId, ratingBody) val response = service.postTvRatingAsUser(id, session.sessionId, ratingBody)
if (response.isSuccessful) { if (response.isSuccessful) {
Log.d(TAG, "Successfully posted rating") Log.d(TAG, "Successfully posted rating")
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
} else { } else {
Log.w(TAG, "Issue posting rating") Log.w(TAG, "Issue posting rating")
} }
@@ -133,13 +132,11 @@ class TvService: KoinComponent, DetailService, HomePageService {
val response = service.deleteTvReviewAsUser(id, session.sessionId) val response = service.deleteTvReviewAsUser(id, session.sessionId)
if (response.isSuccessful) { if (response.isSuccessful) {
Log.d(TAG, "Successfully deleted rating") Log.d(TAG, "Successfully deleted rating")
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
} else { } else {
Log.w(TAG, "Issue deleting rating") Log.w(TAG, "Issue deleting rating")
} }
} }
//todo - turn this into paging
override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> { override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> {
return service.getSimilarTvShows(id, page) return service.getSimilarTvShows(id, page)
} }
@@ -161,41 +158,3 @@ class TvService: KoinComponent, DetailService, HomePageService {
} }
} }
class SimilarTvSource(private val tvId: Int): PagingSource<Int, TmdbItem>(), KoinComponent {
private val service: TvService by inject()
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
return try {
val nextPage = params.key ?: 1
val response = service.getSimilar(tvId, nextPage)
if (response.isSuccessful) {
val responseBody = response.body()
val result = responseBody?.results ?: emptyList()
LoadResult.Page(
data = result,
prevKey = if (nextPage == 1) {
null
} else {
nextPage - 1
},
nextKey = if (result.isEmpty()) {
null
} else {
responseBody?.page?.plus(1) ?: (nextPage + 1)
}
)
} else {
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -1,62 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
import org.koin.core.Koin
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import retrofit2.Response
class HomePagePagingSource(
private val service: HomePageService,
private val mediaFetch: MediaFetchFun,
private val tag: String
): PagingSource<Int, TmdbItem>(), KoinComponent {
companion object {
val TAG = HomePagePagingSource::class.java.simpleName
}
private val context: Context by inject()
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
return try {
val nextPage = params.key ?: 1
Log.d(TAG, "Loading $tag page $nextPage")
val mediaResponse = mediaFetch.invoke(service, nextPage)
if (mediaResponse.isSuccessful) {
val responseBody = mediaResponse.body()
val results = responseBody?.results ?: emptyList()
LoadResult.Page(
data = results,
prevKey = if (nextPage == 1) {
null
} else {
nextPage - 1
},
nextKey = if (results.isEmpty() || responseBody == null) {
null
} else {
responseBody.page + 1
}
)
} else {
Toast.makeText(context, context.getString(R.string.no_result_found), Toast.LENGTH_SHORT).show()
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -1,41 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleApi
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class HomePagePeoplePagingSource: PagingSource<Int, HomePagePerson>(), KoinComponent {
private val service: PeopleService by inject()
override fun getRefreshKey(state: PagingState<Int, HomePagePerson>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, HomePagePerson> {
return try {
val nextPage = params.key ?: 1
val peopleResponse = service.getPopular(page = nextPage)
if (peopleResponse.isSuccessful) {
val responseBody = peopleResponse.body()
val results = responseBody?.results ?: emptyList()
LoadResult.Page(
data = results,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1
)
} else {
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -1,19 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
class RatedEpisode(
type: RatedType,
id: Int,
overview: String,
name: String,
voteAverage: Float,
voteCount: Int,
rating: Float,
releaseDate: String,
@SerializedName("episode_number") val episodeNumber: Int,
@SerializedName("production_code") val productionCode: String?,
@SerializedName("season_number") val seasonNumber: Int,
@SerializedName("show_id") val showId: Int,
@SerializedName("still_path") val stillPath: String?,
): RatedMedia(RatedType.EPISODE, id, overview, name, voteAverage, voteCount, rating, releaseDate)

View File

@@ -1,20 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
abstract class RatedMedia(
var type: RatedType,
@SerializedName("id") val id: Int,
@SerializedName("overview") val overview: String,
@SerializedName("name", alternate = ["title"]) val name: String,
@SerializedName("vote_average") val voteAverage: Float,
@SerializedName("vote_count") val voteCount: Int,
@SerializedName("rating") val rating: Float,
@SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String
) {
enum class RatedType {
MOVIE,
SERIES,
EPISODE
}
}

View File

@@ -1,10 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
class RatedMediaResponse<T: RatedMedia>(
@SerializedName("page") val page: Int,
@SerializedName("results") val results: List<T>,
@SerializedName("total_pages") val totalPages: Int,
@SerializedName("total_results") val totalResults: Int
)

View File

@@ -1,20 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
abstract class RatedTopLevelMedia(
type: RatedType,
id: Int,
overview: String,
name: String,
voteAverage: Float,
voteCount: Int,
rating: Float,
releaseDate: String,
@SerializedName("backdrop_path") val backdropPath: String?,
@SerializedName("genre_ids") val genreIds: List<Int>,
@SerializedName("original_language") val originalLanguage: String,
@SerializedName("original_name", alternate = ["original_title"]) val originalName: String,
@SerializedName("poster_path") val posterPath: String?,
@SerializedName("popularity") val popularity: Float,
): RatedMedia(type, id, overview, name, voteAverage, voteCount, rating, releaseDate)

View File

@@ -1,46 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.utils.types.MediaViewType
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class RecommendedMediaPagingSource(
private val mediaType: MediaViewType
): PagingSource<Int, TmdbItem>(), KoinComponent {
private val preferences: AppPreferences by inject()
private val service: AccountV4Service by inject()
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
return state.anchorPosition
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
return try {
val nextPage = params.key ?: 1
val mediaResponse = if (mediaType == MediaViewType.MOVIE) {
service.getRecommendedMovies(preferences.authorizedSessionValues?.accountId ?: "", nextPage)
} else {
service.getRecommendedTvSeries(preferences.authorizedSessionValues?.accountId ?: "", nextPage)
}
if (mediaResponse.isSuccessful) {
val responseBody = mediaResponse.body()
val results = responseBody?.results ?: emptyList()
LoadResult.Page(
data = results,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1
)
} else {
LoadResult.Invalid()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}

View File

@@ -1,10 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
class WatchlistResponse<T: WatchlistMedia>(
@SerializedName("page") val page: Int,
@SerializedName("results") val results: List<T>,
@SerializedName("total_pages") val totalPages: Int,
@SerializedName("total_results") val totalResults: Int
)

View File

@@ -2,12 +2,14 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedTv import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountResponse import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountResponse
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4RatedMovie import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4RatedTv import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedTv
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedTv
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
@@ -24,23 +26,17 @@ interface AccountV4Api {
@GET("account/{account_id}/tv/favorites") @GET("account/{account_id}/tv/favorites")
suspend fun getFavoriteTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteTvSeries>> suspend fun getFavoriteTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteTvSeries>>
@GET("account/{account_id}/movie/recommendations")
suspend fun getMovieRecommendations(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteMovie>>
@GET("account/{account_id}/tv/recommendations")
suspend fun getTvShowRecommendations(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteTvSeries>>
@GET("account/{account_id}/movie/watchlist") @GET("account/{account_id}/movie/watchlist")
suspend fun getMovieWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteMovie>> suspend fun getMovieWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<WatchlistMovie>>
@GET("account/{account_id}/tv/watchlist") @GET("account/{account_id}/tv/watchlist")
suspend fun getTvShowWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<FavoriteTvSeries>> suspend fun getTvShowWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<WatchlistTvSeries>>
@GET("account/{account_id}/movie/rated") @GET("account/{account_id}/movie/rated")
suspend fun getRatedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<V4RatedMovie>> suspend fun getRatedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<RatedMovie>>
@GET("account/{account_id}/tv/rated") @GET("account/{account_id}/tv/rated")
suspend fun getRatedTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<V4RatedTv>> suspend fun getRatedTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<RatedTv>>
@GET("account/{account_id}/movie/recommendations") @GET("account/{account_id}/movie/recommendations")
suspend fun getRecommendedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<RecommendedMovie>> suspend fun getRecommendedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<RecommendedMovie>>

View File

@@ -3,55 +3,49 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedTv import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountResponse import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountResponse
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4RatedMovie import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4RatedTv import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedTv
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedTv
import retrofit2.Response import retrofit2.Response
class AccountV4Service { class AccountV4Service {
private val service by lazy { TmdbClient().createV4AccountService() } private val service by lazy { TmdbClient().createV4AccountService() }
suspend fun getLists(accountId: String, page: Int = 1): Response<AccountResponse<AccountList>> { suspend fun getLists(accountId: String, page: Int): Response<AccountResponse<AccountList>> {
return service.getLists(accountId, page) return service.getLists(accountId, page)
} }
suspend fun getFavoriteMovies(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteMovie>> { suspend fun getFavoriteMovies(accountId: String, page: Int): Response<AccountResponse<FavoriteMovie>> {
return service.getFavoriteMovies(accountId, page) return service.getFavoriteMovies(accountId, page)
} }
suspend fun getFavoriteTvShows(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteTvSeries>> { suspend fun getFavoriteTvShows(accountId: String, page: Int): Response<AccountResponse<FavoriteTvSeries>> {
return service.getFavoriteTvShows(accountId, page) return service.getFavoriteTvShows(accountId, page)
} }
suspend fun getMovieRecommendations(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteMovie>> { suspend fun getMovieWatchlist(accountId: String, page: Int): Response<AccountResponse<WatchlistMovie>> {
return service.getMovieRecommendations(accountId, page)
}
suspend fun getTvShowRecommendations(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteTvSeries>> {
return service.getTvShowRecommendations(accountId, page)
}
suspend fun getMovieWatchlist(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteMovie>> {
return service.getMovieWatchlist(accountId, page) return service.getMovieWatchlist(accountId, page)
} }
suspend fun getTvShowWatchlist(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteTvSeries>> { suspend fun getTvShowWatchlist(accountId: String, page: Int): Response<AccountResponse<WatchlistTvSeries>> {
return service.getTvShowWatchlist(accountId, page) return service.getTvShowWatchlist(accountId, page)
} }
suspend fun getRatedMovies(accountId: String, page: Int = 1): Response<AccountResponse<V4RatedMovie>> { suspend fun getRatedMovies(accountId: String, page: Int): Response<AccountResponse<RatedMovie>> {
return service.getRatedMovies(accountId, page) return service.getRatedMovies(accountId, page)
} }
suspend fun getRatedTvShows(accountId: String, page: Int = 1): Response<AccountResponse<V4RatedTv>> { suspend fun getRatedTvShows(accountId: String, page: Int): Response<AccountResponse<RatedTv>> {
return service.getRatedTvShows(accountId, page) return service.getRatedTvShows(accountId, page)
} }
suspend fun getRecommendedMovies(accountId: String, page: Int = 1): Response<AccountResponse<RecommendedMovie>> { suspend fun getRecommendedMovies(accountId: String, page: Int ): Response<AccountResponse<RecommendedMovie>> {
return service.getRecommendedMovies(accountId, page) return service.getRecommendedMovies(accountId, page)
} }

View File

@@ -2,14 +2,14 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import com.owenlejeune.tvtime.BuildConfig import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AddToListBody
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.api.v4.model.CreateListBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.* import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsBody
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListUpdateBody
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.api.tmdb.api.v4.model.MediaList
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.UpdateListItemBody
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import retrofit2.Response
class ListV4Service: KoinComponent { class ListV4Service: KoinComponent {
@@ -47,7 +47,6 @@ class ListV4Service: KoinComponent {
suspend fun deleteListItems(listId: Int, body: DeleteListItemsBody) { suspend fun deleteListItems(listId: Int, body: DeleteListItemsBody) {
val response = service.deleteListItems(listId, body) val response = service.deleteListItems(listId, body)
if (response.isSuccessful) { if (response.isSuccessful) {
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.List)
getList(listId) getList(listId)
} }
} }

View File

@@ -2,14 +2,14 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4.model
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
abstract class V4RatedMedia( abstract class RatedMedia(
var type: RatedType, var type: RatedType,
@SerializedName("id") val id: Int, @SerializedName("id") val id: Int,
@SerializedName("overview") val overview: String, @SerializedName("overview") val overview: String,
@SerializedName("name", alternate = ["title"]) val name: String, @SerializedName("name", alternate = ["title"]) val name: String,
@SerializedName("vote_average") val voteAverage: Float, @SerializedName("vote_average") val voteAverage: Float,
@SerializedName("vote_count") val voteCount: Int, @SerializedName("vote_count") val voteCount: Int,
@SerializedName("rating") val rating: AccountRating, @SerializedName("account_rating") val rating: AccountRating,
@SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String, @SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String,
@SerializedName("backdrop_path") val backdropPath: String?, @SerializedName("backdrop_path") val backdropPath: String?,
@SerializedName("genre_ids") val genreIds: List<Int>, @SerializedName("genre_ids") val genreIds: List<Int>,
@@ -25,7 +25,7 @@ abstract class V4RatedMedia(
} }
inner class AccountRating( inner class AccountRating(
@SerializedName("value") val rating: Int, @SerializedName("value") val value: Float,
@SerializedName("created_at") val createdAt: String @SerializedName("created_at") val createdAt: String
) )
} }

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model package com.owenlejeune.tvtime.api.tmdb.api.v4.model
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -8,7 +8,7 @@ class RatedMovie(
name: String, name: String,
voteAverage: Float, voteAverage: Float,
voteCount: Int, voteCount: Int,
rating: Float, rating: AccountRating,
backdropPath: String?, backdropPath: String?,
genreIds: List<Int>, genreIds: List<Int>,
originalLanguage: String, originalLanguage: String,
@@ -17,8 +17,8 @@ class RatedMovie(
popularity: Float, popularity: Float,
releaseDate: String, releaseDate: String,
@SerializedName("adult") val isAdult: Boolean, @SerializedName("adult") val isAdult: Boolean,
@SerializedName("video") val video: Boolean, @SerializedName("video") val video: Boolean
): RatedTopLevelMedia( ): RatedMedia(
RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating, releaseDate, RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating, releaseDate,
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
) )

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model package com.owenlejeune.tvtime.api.tmdb.api.v4.model
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@@ -8,7 +8,7 @@ class RatedTv(
name: String, name: String,
voteAverage: Float, voteAverage: Float,
voteCount: Int, voteCount: Int,
rating: Float, rating: AccountRating,
backdropPath: String?, backdropPath: String?,
genreIds: List<Int>, genreIds: List<Int>,
originalLanguage: String, originalLanguage: String,
@@ -17,7 +17,7 @@ class RatedTv(
popularity: Float, popularity: Float,
releaseDate: String, releaseDate: String,
@SerializedName("origin_country") val originCountry: List<String>, @SerializedName("origin_country") val originCountry: List<String>,
): RatedTopLevelMedia( ): RatedMedia(
RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating, releaseDate, RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating, releaseDate,
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
) )

View File

@@ -1,24 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v4.model
import com.google.gson.annotations.SerializedName
class V4RatedMovie(
id: Int,
overview: String,
name: String,
voteAverage: Float,
voteCount: Int,
rating: AccountRating,
backdropPath: String?,
genreIds: List<Int>,
originalLanguage: String,
originalName: String,
posterPath: String?,
popularity: Float,
releaseDate: String,
@SerializedName("adult") val isAdult: Boolean,
@SerializedName("video") val video: Boolean
): V4RatedMedia(
RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating, releaseDate,
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
)

View File

@@ -1,23 +0,0 @@
package com.owenlejeune.tvtime.api.tmdb.api.v4.model
import com.google.gson.annotations.SerializedName
class V4RatedTv(
id: Int,
overview: String,
name: String,
voteAverage: Float,
voteCount: Int,
rating: AccountRating,
backdropPath: String?,
genreIds: List<Int>,
originalLanguage: String,
originalName: String,
posterPath: String?,
popularity: Float,
releaseDate: String,
@SerializedName("origin_country") val originCountry: List<String>,
): V4RatedMedia(
RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating, releaseDate,
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
)

View File

@@ -74,9 +74,7 @@ fun ActionsView(
} }
Row( Row(
modifier = modifier modifier = modifier,
.wrapContentSize()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
if (actions.contains(Actions.RATE)) { if (actions.contains(Actions.RATE)) {

View File

@@ -1,7 +1,6 @@
package com.owenlejeune.tvtime.ui.components package com.owenlejeune.tvtime.ui.components
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -34,7 +33,13 @@ fun RatingDialog(
AlertDialog( AlertDialog(
modifier = Modifier.wrapContentHeight(), modifier = Modifier.wrapContentHeight(),
onDismissRequest = { showDialog.value = false }, onDismissRequest = { showDialog.value = false },
title = { Text(text = stringResource(R.string.rating_dialog_title)) }, title = {
if (rating > 0f) {
Text(text = stringResource(id = R.string.my_rating_dialog_title))
} else {
Text(text = stringResource(R.string.rating_dialog_title))
}
},
confirmButton = { confirmButton = {
Button( Button(
modifier = Modifier.height(40.dp), modifier = Modifier.height(40.dp),

View File

@@ -5,17 +5,14 @@ import androidx.navigation.NavHostController
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries import com.owenlejeune.tvtime.api.tmdb.api.v3.model.FavoriteTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedEpisode
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatedTv
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistTvSeries
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedTv
import com.owenlejeune.tvtime.ui.screens.AccountTabContent import com.owenlejeune.tvtime.ui.screens.AccountTabContent
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.ui.screens.RecommendedAccountTabContent
import com.owenlejeune.tvtime.utils.ResourceUtils import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TabNavItem import com.owenlejeune.tvtime.utils.types.TabNavItem
import org.koin.core.component.inject import org.koin.core.component.inject
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -26,8 +23,8 @@ sealed class AccountTabNavItem(
noContentStringRes: Int, noContentStringRes: Int,
val mediaType: MediaViewType, val mediaType: MediaViewType,
val screen: AccountNavComposableFun, val screen: AccountNavComposableFun,
val listFetchFun: ListFetchFun, val type: AccountTabType,
val listType: KClass<*>, val contentType: KClass<*>,
val ordinal: Int val ordinal: Int
): TabNavItem(route) { ): TabNavItem(route) {
private val resourceUtils: ResourceUtils by inject() private val resourceUtils: ResourceUtils by inject()
@@ -35,10 +32,18 @@ sealed class AccountTabNavItem(
override val name = resourceUtils.getString(stringRes) override val name = resourceUtils.getString(stringRes)
val noContentText = resourceUtils.getString(noContentStringRes) val noContentText = resourceUtils.getString(noContentStringRes)
enum class AccountTabType {
RATED,
FAVORITE,
WATCHLIST,
LIST,
RECOMMENDED
}
companion object { companion object {
val AuthorizedItems val AuthorizedItems
get() = listOf( get() = listOf(
RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows, RatedMovies, RatedTvShows, /*RatedTvEpisodes, */FavoriteMovies, FavoriteTvShows,
MovieWatchlist, TvWatchlist, UserLists, RecommendedMovies, RecommendedTv MovieWatchlist, TvWatchlist, UserLists, RecommendedMovies, RecommendedTv
).filter { it.ordinal > -1 }.sortedBy { it.ordinal } ).filter { it.ordinal > -1 }.sortedBy { it.ordinal }
} }
@@ -49,7 +54,7 @@ sealed class AccountTabNavItem(
R.string.no_rated_movies, R.string.no_rated_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, screenContent,
{ SessionManager.currentSession.value?.ratedMovies ?: emptyList() }, AccountTabType.RATED,
RatedMovie::class, RatedMovie::class,
0 0
) )
@@ -59,27 +64,27 @@ sealed class AccountTabNavItem(
R.string.no_rated_tv, R.string.no_rated_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, screenContent,
{ SessionManager.currentSession.value?.ratedTvShows ?: emptyList() }, AccountTabType.RATED,
RatedTv::class, RatedTv::class,
1 1
) )
object RatedTvEpisodes: AccountTabNavItem( // object RatedTvEpisodes: AccountTabNavItem(
R.string.nav_rated_episodes_title, // R.string.nav_rated_episodes_title,
"rated_episodes_route", // "rated_episodes_route",
R.string.no_rated_episodes, // R.string.no_rated_episodes,
MediaViewType.EPISODE, // MediaViewType.EPISODE,
screenContent, // screenContent,
{ SessionManager.currentSession.value?.ratedTvEpisodes ?: emptyList() }, // AccountTabType.RATED,
RatedEpisode::class, // RatedEpisode::class,
-1 //2 // -1 //2
) // )
object FavoriteMovies: AccountTabNavItem( object FavoriteMovies: AccountTabNavItem(
R.string.nav_favorite_movies_title, R.string.nav_favorite_movies_title,
"favorite_movies_route", "favorite_movies_route",
R.string.no_favorite_movies, R.string.no_favorite_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, screenContent,
{ SessionManager.currentSession.value?.favoriteMovies ?: emptyList() }, AccountTabType.FAVORITE,
FavoriteMovie::class, FavoriteMovie::class,
3 3
) )
@@ -89,7 +94,7 @@ sealed class AccountTabNavItem(
R.string.no_favorite_tv, R.string.no_favorite_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, screenContent,
{ SessionManager.currentSession.value?.favoriteTvShows ?: emptyList() }, AccountTabType.FAVORITE,
FavoriteTvSeries::class, FavoriteTvSeries::class,
4 4
) )
@@ -99,7 +104,7 @@ sealed class AccountTabNavItem(
R.string.no_watchlist_movies, R.string.no_watchlist_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, screenContent,
{ SessionManager.currentSession.value?.movieWatchlist ?: emptyList() }, AccountTabType.WATCHLIST,
WatchlistMovie::class, WatchlistMovie::class,
5 5
) )
@@ -109,7 +114,7 @@ sealed class AccountTabNavItem(
R.string.no_watchlist_tv, R.string.no_watchlist_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, screenContent,
{ SessionManager.currentSession.value?.tvWatchlist ?: emptyList() }, AccountTabType.WATCHLIST,
WatchlistTvSeries::class, WatchlistTvSeries::class,
6 6
) )
@@ -120,7 +125,7 @@ sealed class AccountTabNavItem(
R.string.no_lists, R.string.no_lists,
MediaViewType.LIST, MediaViewType.LIST,
screenContent, screenContent,
{ SessionManager.currentSession.value?.accountLists ?: emptyList() }, AccountTabType.LIST,
AccountList::class, AccountList::class,
7 7
) )
@@ -130,8 +135,8 @@ sealed class AccountTabNavItem(
"recommended_movies_route", "recommended_movies_route",
R.string.no_recommended_movies, R.string.no_recommended_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
recommendedScreenContent, screenContent,
{ emptyList() }, AccountTabType.RECOMMENDED,
AccountList::class, AccountList::class,
8 8
) )
@@ -141,31 +146,21 @@ sealed class AccountTabNavItem(
"recommended_tv_route", "recommended_tv_route",
R.string.no_recommended_tv, R.string.no_recommended_tv,
MediaViewType.TV, MediaViewType.TV,
recommendedScreenContent, screenContent,
{ emptyList() }, AccountTabType.RECOMMENDED,
AccountList::class, AccountList::class,
9 9
) )
} }
private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, listFetchFun, clazz -> private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, atType, clazz ->
AccountTabContent( AccountTabContent(
noContentText = noContentText, noContentText = noContentText,
appNavController = appNavController, appNavController = appNavController,
mediaViewType = mediaViewType, mediaViewType = mediaViewType,
listFetchFun = listFetchFun, accountTabType = atType,
clazz = clazz clazz = clazz
) )
} }
private val recommendedScreenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, _, _ -> typealias AccountNavComposableFun = @Composable (String, NavHostController, MediaViewType, AccountTabNavItem.AccountTabType, KClass<*>) -> Unit
RecommendedAccountTabContent(
noContentText = noContentText,
appNavController = appNavController,
mediaViewType = mediaViewType,
)
}
typealias ListFetchFun = () -> List<Any>
typealias AccountNavComposableFun = @Composable (String, NavHostController, MediaViewType, ListFetchFun, KClass<*>) -> Unit

View File

@@ -5,11 +5,11 @@ import androidx.navigation.NavHostController
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.ui.viewmodel.MediaTabViewModel
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabContent import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabContent
import com.owenlejeune.tvtime.utils.ResourceUtils import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TabNavItem import com.owenlejeune.tvtime.utils.types.TabNavItem
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import org.koin.core.component.inject import org.koin.core.component.inject
import retrofit2.Response import retrofit2.Response
@@ -17,17 +17,31 @@ sealed class MediaTabNavItem(
stringRes: Int, stringRes: Int,
route: String, route: String,
val screen: MediaNavComposableFun, val screen: MediaNavComposableFun,
val movieViewModel: MediaTabViewModel?, val type: Type
val tvViewModel: MediaTabViewModel?
): TabNavItem(route) { ): TabNavItem(route) {
private val resourceUtils: ResourceUtils by inject() private val resourceUtils: ResourceUtils by inject()
override val name = resourceUtils.getString(stringRes) override val name = resourceUtils.getString(stringRes)
enum class Type {
POPULAR,
NOW_PLAYING,
UPCOMING,
TOP_RATED
}
companion object { companion object {
val MovieItems = listOf(NowPlaying, Popular, Upcoming, TopRated) val MovieItems = listOf(NowPlaying, Popular, Upcoming, TopRated)
val TvItems = listOf(OnTheAir, Popular, AiringToday, TopRated) val TvItems = listOf(OnTheAir, Popular, AiringToday, TopRated)
fun itemsForType(type: MediaViewType): List<MediaTabNavItem> {
return when (type) {
MediaViewType.MOVIE -> MovieItems
MediaViewType.TV -> TvItems
else -> throw ViewableMediaTypeException(type)
}
}
private val Items = listOf(NowPlaying, Popular, TopRated, Upcoming, AiringToday, OnTheAir) private val Items = listOf(NowPlaying, Popular, TopRated, Upcoming, AiringToday, OnTheAir)
fun getByRoute(route: String?): MediaTabNavItem? { fun getByRoute(route: String?): MediaTabNavItem? {
@@ -39,43 +53,37 @@ sealed class MediaTabNavItem(
stringRes = R.string.nav_popular_title, stringRes = R.string.nav_popular_title,
route = "popular_route", route = "popular_route",
screen = screenContent, screen = screenContent,
movieViewModel = MediaTabViewModel.PopularMoviesVM, type = Type.POPULAR
tvViewModel = MediaTabViewModel.PopularTvVM
) )
object TopRated: MediaTabNavItem( object TopRated: MediaTabNavItem(
stringRes = R.string.nav_top_rated_title, stringRes = R.string.nav_top_rated_title,
route = "top_rated_route", route = "top_rated_route",
screen = screenContent, screen = screenContent,
movieViewModel = MediaTabViewModel.TopRatedMoviesVM, type = Type.TOP_RATED
tvViewModel = MediaTabViewModel.TopRatedTvVM
) )
object NowPlaying: MediaTabNavItem( object NowPlaying: MediaTabNavItem(
stringRes = R.string.nav_now_playing_title, stringRes = R.string.nav_now_playing_title,
route = "now_playing_route", route = "now_playing_route",
screen = screenContent, screen = screenContent,
movieViewModel = MediaTabViewModel.NowPlayingMoviesVM, type = Type.NOW_PLAYING
tvViewModel = null
) )
object Upcoming: MediaTabNavItem( object Upcoming: MediaTabNavItem(
stringRes = R.string.nav_upcoming_title, stringRes = R.string.nav_upcoming_title,
route = "upcoming_route", route = "upcoming_route",
screen = screenContent, screen = screenContent,
movieViewModel = MediaTabViewModel.UpcomingMoviesVM, type = Type.UPCOMING
tvViewModel = null
) )
object AiringToday: MediaTabNavItem( object AiringToday: MediaTabNavItem(
stringRes = R.string.nav_tv_airing_today_title, stringRes = R.string.nav_tv_airing_today_title,
route = "airing_today_route", route = "airing_today_route",
screen = screenContent, screen = screenContent,
movieViewModel = null, type = Type.NOW_PLAYING
tvViewModel = MediaTabViewModel.AiringTodayTvVM
) )
object OnTheAir: MediaTabNavItem( object OnTheAir: MediaTabNavItem(
stringRes = R.string.nav_tv_on_the_air, stringRes = R.string.nav_tv_on_the_air,
route = "on_the_air_route", route = "on_the_air_route",
screen = screenContent, screen = screenContent,
movieViewModel = null, type = Type.UPCOMING
tvViewModel = MediaTabViewModel.OnTheAirTvVM
) )
} }

View File

@@ -20,7 +20,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
@@ -30,6 +32,10 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMedia
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedTv
import com.owenlejeune.tvtime.extensions.lazyPagingItems
import com.owenlejeune.tvtime.extensions.unlessEmpty import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.ui.components.AccountIcon import com.owenlejeune.tvtime.ui.components.AccountIcon
import com.owenlejeune.tvtime.ui.components.MediaResultCard import com.owenlejeune.tvtime.ui.components.MediaResultCard
@@ -37,8 +43,7 @@ import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.ScrollableTabs import com.owenlejeune.tvtime.ui.components.ScrollableTabs
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.AppNavItem import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
import com.owenlejeune.tvtime.ui.viewmodel.RecommendedMediaViewModel
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
@@ -177,12 +182,13 @@ fun <T: Any> AccountTabContent(
noContentText: String, noContentText: String,
appNavController: NavHostController, appNavController: NavHostController,
mediaViewType: MediaViewType, mediaViewType: MediaViewType,
listFetchFun: ListFetchFun, accountTabType: AccountTabNavItem.AccountTabType,
clazz: KClass<T> clazz: KClass<T>
) { ) {
val contentItems = remember { listFetchFun() } val accountViewModel = viewModel<AccountViewModel>()
val contentItems = accountViewModel.getPagingFlowFor(mediaViewType, accountTabType).collectAsLazyPagingItems()
if (contentItems.isEmpty()) { if (contentItems.itemCount == 0) {
Column { Column {
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text( Text(
@@ -195,6 +201,15 @@ fun <T: Any> AccountTabContent(
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
} }
} else if (accountTabType == AccountTabNavItem.AccountTabType.RECOMMENDED) {
PagingPosterGrid(
lazyPagingItems = contentItems as LazyPagingItems<TmdbItem>,
onClick = { id ->
appNavController.navigate(
AppNavItem.DetailView.withArgs(mediaViewType, id)
)
}
)
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
@@ -202,10 +217,10 @@ fun <T: Any> AccountTabContent(
.padding(12.dp), .padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
items(contentItems.size) { i -> lazyPagingItems(contentItems) { i ->
when (clazz) { when (clazz) {
RatedTv::class, RatedMovie::class -> { RatedTv::class, RatedMovie::class -> {
val item = contentItems[i] as RatedTopLevelMedia val item = i as RatedMedia
MediaItemRow( MediaItemRow(
appNavController = appNavController, appNavController = appNavController,
mediaViewType = mediaViewType, mediaViewType = mediaViewType,
@@ -214,24 +229,12 @@ fun <T: Any> AccountTabContent(
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath), backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath),
name = item.name, name = item.name,
date = item.releaseDate, date = item.releaseDate,
rating = item.rating, rating = item.rating.value,
description = item.overview
)
}
RatedEpisode::class -> {
val item = contentItems[i] as RatedMedia
MediaItemRow(
appNavController = appNavController,
mediaViewType = mediaViewType,
id = item.id,
name = item.name,
date = item.releaseDate,
rating = item.rating,
description = item.overview description = item.overview
) )
} }
FavoriteMovie::class, FavoriteTvSeries::class -> { FavoriteMovie::class, FavoriteTvSeries::class -> {
val item = contentItems[i] as FavoriteMedia val item = i as FavoriteMedia
MediaItemRow( MediaItemRow(
appNavController = appNavController, appNavController = appNavController,
mediaViewType = mediaViewType, mediaViewType = mediaViewType,
@@ -244,7 +247,7 @@ fun <T: Any> AccountTabContent(
) )
} }
WatchlistMovie::class, WatchlistTvSeries::class -> { WatchlistMovie::class, WatchlistTvSeries::class -> {
val item = contentItems[i] as WatchlistMedia val item = i as WatchlistMedia
MediaItemRow( MediaItemRow(
appNavController = appNavController, appNavController = appNavController,
mediaViewType = mediaViewType, mediaViewType = mediaViewType,
@@ -257,7 +260,7 @@ fun <T: Any> AccountTabContent(
) )
} }
AccountList::class -> { AccountList::class -> {
val item = contentItems[i] as AccountList val item = i as AccountList
MediaItemRow( MediaItemRow(
appNavController = appNavController, appNavController = appNavController,
mediaViewType = mediaViewType, mediaViewType = mediaViewType,
@@ -275,43 +278,6 @@ fun <T: Any> AccountTabContent(
} }
} }
@Composable
fun RecommendedAccountTabContent(
noContentText: String,
appNavController: NavHostController,
mediaViewType: MediaViewType,
) {
val viewModel = when (mediaViewType) {
MediaViewType.MOVIE -> RecommendedMediaViewModel.RecommendedMoviesVM
MediaViewType.TV -> RecommendedMediaViewModel.RecommendedTvVM
else -> throw IllegalArgumentException("Media type given: ${mediaViewType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
}
val mediaListItems = viewModel.mediaItems.collectAsLazyPagingItems()
if (mediaListItems.itemCount < 1) {
Column {
Spacer(modifier = Modifier.weight(1f))
Text(
modifier = Modifier.fillMaxWidth(),
text = noContentText,
color = MaterialTheme.colorScheme.onBackground,
fontSize = 22.sp,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.weight(1f))
}
} else {
PagingPosterGrid(
lazyPagingItems = mediaListItems,
onClick = { id ->
appNavController.navigate(
AppNavItem.DetailView.withArgs(mediaViewType, id)
)
}
)
}
}
@Composable @Composable
private fun MediaItemRow( private fun MediaItemRow(
appNavController: NavHostController, appNavController: NavHostController,
@@ -345,6 +311,6 @@ fun AccountTabs(
appNavController: NavHostController appNavController: NavHostController
) { ) {
HorizontalPager(count = tabs.size, state = pagerState) { page -> HorizontalPager(count = tabs.size, state = pagerState) { page ->
tabs[page].screen(tabs[page].noContentText, appNavController, tabs[page].mediaType, tabs[page].listFetchFun, tabs[page].listType) tabs[page].screen(tabs[page].noContentText, appNavController, tabs[page].mediaType, tabs[page].type, tabs[page].contentType)
} }
} }

View File

@@ -90,7 +90,6 @@ fun ListDetailScreen(
val listMap = remember { accountViewModel.listMap } val listMap = remember { accountViewModel.listMap }
val parentList = listMap[itemId] val parentList = listMap[itemId]
val decayAnimationSpec = rememberSplineBasedDecay<Float>() val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState() val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) { val scrollBehavior = remember(decayAnimationSpec) {
@@ -133,9 +132,7 @@ fun ListDetailScreen(
val selectedSortOrder = remember { mutableStateOf(mediaList.sortBy) } val selectedSortOrder = remember { mutableStateOf(mediaList.sortBy) }
ListHeader( ListHeader(
list = mediaList, list = mediaList,
selectedSortOrder = selectedSortOrder, selectedSortOrder = selectedSortOrder
service = service,
parentList = parentList
) )
val sortedResults = selectedSortOrder.value.sort(mediaList.results) val sortedResults = selectedSortOrder.value.sort(mediaList.results)
@@ -155,9 +152,7 @@ fun ListDetailScreen(
@Composable @Composable
private fun ListHeader( private fun ListHeader(
list: MediaList, list: MediaList,
selectedSortOrder: MutableState<SortOrder>, selectedSortOrder: MutableState<SortOrder>
service: ListV4Service,
parentList: MediaList?
) { ) {
val context = LocalContext.current val context = LocalContext.current

View File

@@ -204,7 +204,7 @@ private fun MediaViewContent(
val currentSession = remember { SessionManager.currentSession } val currentSession = remember { SessionManager.currentSession }
currentSession.value?.let { currentSession.value?.let {
ActionsView(itemId = itemId, type = type) ActionsView(itemId = itemId, type = type, modifier = Modifier.padding(start = 20.dp))
} }
if (type == MediaViewType.MOVIE) { if (type == MediaViewType.MOVIE) {

View File

@@ -1,9 +1,7 @@
package com.owenlejeune.tvtime.ui.screens.tabs package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -16,11 +14,11 @@ import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.SearchView import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.components.Tabs
import com.owenlejeune.tvtime.ui.navigation.AppNavItem import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
import com.owenlejeune.tvtime.ui.components.Tabs
import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel
import com.owenlejeune.tvtime.ui.viewmodel.MediaTabViewModel import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@@ -44,11 +42,7 @@ fun MediaTab(
mediaType = mediaType mediaType = mediaType
) )
val tabs = when (mediaType) { val tabs = MediaTabNavItem.itemsForType(type = mediaType)
MediaViewType.MOVIE -> MediaTabNavItem.MovieItems
MediaViewType.TV -> MediaTabNavItem.TvItems
else -> throw IllegalArgumentException("Media type given: ${mediaType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
}
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState) Tabs(tabs = tabs, pagerState = pagerState)
MediaTabs( MediaTabs(
@@ -61,13 +55,13 @@ fun MediaTab(
} }
@Composable @Composable
fun MediaTabContent(appNavController: NavHostController, mediaType: MediaViewType, mediaTabItem: MediaTabNavItem) { fun MediaTabContent(
val viewModel: MediaTabViewModel? = when(mediaType) { appNavController: NavHostController,
MediaViewType.MOVIE -> mediaTabItem.movieViewModel mediaType: MediaViewType,
MediaViewType.TV -> mediaTabItem.tvViewModel mediaTabItem: MediaTabNavItem
else -> throw IllegalArgumentException("Media type given: ${mediaType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen ) {
} val viewModel = viewModel<MainViewModel>()
val mediaListItems = viewModel?.mediaItems?.collectAsLazyPagingItems() val mediaListItems = viewModel.produceFlowFor(mediaType, mediaTabItem.type).collectAsLazyPagingItems()
PagingPosterGrid( PagingPosterGrid(
lazyPagingItems = mediaListItems, lazyPagingItems = mediaListItems,

View File

@@ -1,9 +1,7 @@
package com.owenlejeune.tvtime.ui.screens.tabs package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -13,7 +11,7 @@ import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid
import com.owenlejeune.tvtime.ui.components.SearchView import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.navigation.AppNavItem import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel
import com.owenlejeune.tvtime.ui.viewmodel.PeopleTabViewModel import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
@Composable @Composable
@@ -30,7 +28,8 @@ fun PeopleTab(
mediaType = MediaViewType.PERSON mediaType = MediaViewType.PERSON
) )
val peopleList = PeopleTabViewModel().popularPeople.collectAsLazyPagingItems() val mainViewModel = viewModel<MainViewModel>()
val peopleList = mainViewModel.popularPeople.collectAsLazyPagingItems()
PagingPeoplePosterGrid( PagingPeoplePosterGrid(
lazyPagingItems = peopleList, lazyPagingItems = peopleList,

View File

@@ -1,15 +1,21 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.ui.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.owenlejeune.tvtime.api.tmdb.api.createPagingFlow
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MarkAsFavoriteBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MarkAsFavoriteBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchlistBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsItem import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsItem
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListUpdateBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListUpdateBody
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@@ -17,9 +23,94 @@ class AccountViewModel: ViewModel(), KoinComponent {
private val listService: ListV4Service by inject() private val listService: ListV4Service by inject()
private val accountService: AccountService by inject() private val accountService: AccountService by inject()
private val accountV4Service: AccountV4Service by inject()
private val accountId: String
get() = SessionManager.currentSession.value?.accountId ?: ""
val listMap = listService.listMap val listMap = listService.listMap
val ratedTv: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getRatedTvShows(accountId, p) },
processor = { it.results }
)
val favoriteTv: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getFavoriteTvShows(accountId, p) },
processor = { it.results }
)
val watchlistTv: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getTvShowWatchlist(accountId, p) },
processor = { it.results }
)
val recommendedTv: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getRecommendedTvSeries(accountId, p) },
processor = { it.results }
)
val ratedMovies: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getRatedMovies(accountId, p) },
processor = { it.results }
)
val favoriteMovies: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getFavoriteMovies(accountId, p) },
processor = { it.results }
)
val watchlistMovies: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getMovieWatchlist(accountId, p) },
processor = { it.results }
)
val recommendedMovies: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getRecommendedMovies(accountId, p) },
processor = { it.results }
)
val userLists: Flow<PagingData<Any>> = createPagingFlow(
fetcher = { p -> accountV4Service.getLists(accountId, p) },
processor = { it.results }
)
fun getPagingFlowFor(type: MediaViewType, accountTabType: AccountTabNavItem.AccountTabType): Flow<PagingData<Any>> {
return when (accountTabType) {
AccountTabNavItem.AccountTabType.LIST -> userLists
AccountTabNavItem.AccountTabType.RATED -> {
when (type) {
MediaViewType.MOVIE -> ratedMovies
MediaViewType.TV -> ratedTv
else -> throw ViewableMediaTypeException(type)
}
}
AccountTabNavItem.AccountTabType.FAVORITE -> {
when (type) {
MediaViewType.MOVIE -> favoriteMovies
MediaViewType.TV -> favoriteTv
else -> throw ViewableMediaTypeException(type)
}
}
AccountTabNavItem.AccountTabType.WATCHLIST -> {
when (type) {
MediaViewType.MOVIE -> watchlistMovies
MediaViewType.TV -> watchlistTv
else -> throw ViewableMediaTypeException(type)
}
}
AccountTabNavItem.AccountTabType.RECOMMENDED -> {
when (type) {
MediaViewType.MOVIE -> recommendedMovies
MediaViewType.TV -> recommendedTv
else -> throw ViewableMediaTypeException(type)
}
}
}
}
fun getRecommendedFor(type: MediaViewType): Flow<PagingData<Any>> {
return when (type) {
MediaViewType.MOVIE -> recommendedMovies
MediaViewType.TV -> recommendedTv
else -> throw ViewableMediaTypeException(type)
}
}
suspend fun getList(listId: Int) { suspend fun getList(listId: Int) {
listService.getList(listId = listId) listService.getList(listId = listId)
} }

View File

@@ -1,15 +1,10 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.ui.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import com.owenlejeune.tvtime.api.tmdb.api.createPagingFlow
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
import com.owenlejeune.tvtime.api.tmdb.api.v3.SimilarMoviesSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.SimilarTvSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember
@@ -23,6 +18,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Review
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -48,6 +44,23 @@ class MainViewModel: ViewModel(), KoinComponent {
val similarMovies = movieService.similar val similarMovies = movieService.similar
val movieAccountStates = movieService.accountStates val movieAccountStates = movieService.accountStates
val popularMovies = createPagingFlow(
fetcher = { p -> movieService.getPopular(p) },
processor = { it.results }
)
val topRatedMovies = createPagingFlow(
fetcher = { p -> movieService.getTopRated(p) },
processor = { it.results }
)
val nowPlayingMovies = createPagingFlow(
fetcher = { p -> movieService.getNowPlaying(p) },
processor = { it.results }
)
val upcomingMovies = createPagingFlow(
fetcher = { p -> movieService.getUpcoming(p) },
processor = { it.results }
)
val detailedTv = tvService.detailTv val detailedTv = tvService.detailTv
val tvImages = tvService.images val tvImages = tvService.images
val tvCast = tvService.cast val tvCast = tvService.cast
@@ -62,12 +75,34 @@ class MainViewModel: ViewModel(), KoinComponent {
val similarTv = tvService.similar val similarTv = tvService.similar
val tvAccountStates = tvService.accountStates val tvAccountStates = tvService.accountStates
val popularTv = createPagingFlow(
fetcher = { p -> tvService.getPopular(p) },
processor = { it.results }
)
val topRatedTv = createPagingFlow(
fetcher = { p -> tvService.getTopRated(p) },
processor = { it.results }
)
val airingTodayTv = createPagingFlow(
fetcher = { p -> tvService.getNowPlaying(p) },
processor = { it.results }
)
val onTheAirTv = createPagingFlow(
fetcher = { p -> tvService.getUpcoming(p) },
processor = { it.results }
)
val peopleMap = peopleService.peopleMap val peopleMap = peopleService.peopleMap
val peopleCastMap = peopleService.castMap val peopleCastMap = peopleService.castMap
val peopleCrewMap = peopleService.crewMap val peopleCrewMap = peopleService.crewMap
val peopleImagesMap = peopleService.imagesMap val peopleImagesMap = peopleService.imagesMap
val peopleExternalIdsMap = peopleService.externalIdsMap val peopleExternalIdsMap = peopleService.externalIdsMap
val popularPeople = createPagingFlow(
fetcher = { p -> peopleService.getPopular(p) },
processor = { it.results }
)
private fun <T> providesForType(type: MediaViewType, movies: () -> T, tv: () -> T): T { private fun <T> providesForType(type: MediaViewType, movies: () -> T, tv: () -> T): T {
return when (type) { return when (type) {
MediaViewType.MOVIE -> movies() MediaViewType.MOVIE -> movies()
@@ -120,6 +155,28 @@ class MainViewModel: ViewModel(), KoinComponent {
return providesForType(type, { movieAccountStates }, { tvAccountStates} ) return providesForType(type, { movieAccountStates }, { tvAccountStates} )
} }
fun produceFlowFor(mediaType: MediaViewType, contentType: MediaTabNavItem.Type): Flow<PagingData<TmdbItem>> {
return providesForType(
mediaType,
{
when (contentType) {
MediaTabNavItem.Type.UPCOMING -> upcomingMovies
MediaTabNavItem.Type.TOP_RATED -> topRatedMovies
MediaTabNavItem.Type.NOW_PLAYING -> nowPlayingMovies
MediaTabNavItem.Type.POPULAR -> popularMovies
}
},
{
when (contentType) {
MediaTabNavItem.Type.UPCOMING -> onTheAirTv
MediaTabNavItem.Type.TOP_RATED -> topRatedTv
MediaTabNavItem.Type.NOW_PLAYING -> airingTodayTv
MediaTabNavItem.Type.POPULAR -> popularTv
}
}
)
}
suspend fun getById(id: Int, type: MediaViewType) { suspend fun getById(id: Int, type: MediaViewType) {
when (type) { when (type) {
MediaViewType.MOVIE -> movieService.getById(id) MediaViewType.MOVIE -> movieService.getById(id)
@@ -215,14 +272,16 @@ class MainViewModel: ViewModel(), KoinComponent {
fun getSimilar(id: Int, type: MediaViewType) { fun getSimilar(id: Int, type: MediaViewType) {
when (type) { when (type) {
MediaViewType.MOVIE -> { MediaViewType.MOVIE -> {
similarMovies[id] = Pager(PagingConfig(pageSize = 1)) { similarMovies[id] = createPagingFlow(
SimilarMoviesSource(id) fetcher = { p -> movieService.getSimilar(id, p) },
}.flow.cachedIn(viewModelScope) processor = { it.results }
)
} }
MediaViewType.TV -> { MediaViewType.TV -> {
similarTv[id] = Pager(PagingConfig(pageSize = 1)) { similarTv[id] = createPagingFlow(
SimilarTvSource(id) fetcher = { p -> tvService.getSimilar(id, p) },
}.flow.cachedIn(viewModelScope) processor = { it.results }
)
} }
else -> {} else -> {}
} }

View File

@@ -1,34 +0,0 @@
package com.owenlejeune.tvtime.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePagingSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.java.KoinJavaComponent.get
sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun, tag: String): ViewModel(), KoinComponent {
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun, tag = tag)
}.flow.cachedIn(viewModelScope)
object PopularMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getPopular(p) }, PopularMoviesVM::class.java.simpleName)
object TopRatedMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getTopRated(p) }, TopRatedMoviesVM::class.java.simpleName)
object NowPlayingMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getNowPlaying(p) }, NowPlayingMoviesVM::class.java.simpleName)
object UpcomingMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getUpcoming(p) }, UpcomingMoviesVM::class.java.simpleName)
object PopularTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getPopular(p) }, PopularTvVM::class.java.simpleName)
object TopRatedTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getTopRated(p) }, TopRatedTvVM::class.java.simpleName)
object AiringTodayTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getNowPlaying(p) }, AiringTodayTvVM::class.java.simpleName)
object OnTheAirTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getUpcoming(p) }, OnTheAirTvVM::class.java.simpleName)
}

View File

@@ -1,19 +0,0 @@
package com.owenlejeune.tvtime.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeoplePagingSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePerson
import kotlinx.coroutines.flow.Flow
class PeopleTabViewModel: ViewModel() {
val popularPeople: Flow<PagingData<HomePagePerson>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
HomePagePeoplePagingSource()
}.flow.cachedIn(viewModelScope)
}

View File

@@ -1,24 +0,0 @@
package com.owenlejeune.tvtime.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RecommendedMediaPagingSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.utils.types.MediaViewType
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
sealed class RecommendedMediaViewModel(mediaType: MediaViewType): ViewModel(), KoinComponent {
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
RecommendedMediaPagingSource(mediaType)
}.flow.cachedIn(viewModelScope)
object RecommendedMoviesVM: RecommendedMediaViewModel(MediaViewType.MOVIE)
object RecommendedTvVM: RecommendedMediaViewModel(MediaViewType.TV)
}

View File

@@ -1,22 +1,12 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.ui.viewmodel
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import com.owenlejeune.tvtime.api.tmdb.api.createPagingFlow
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchPagingSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchResultProvider import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchResultProvider
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMovie
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultTv
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
import com.owenlejeune.tvtime.utils.types.MediaViewType import com.owenlejeune.tvtime.utils.types.MediaViewType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@@ -48,23 +38,26 @@ class SearchViewModel: ViewModel(), KoinComponent {
} }
fun searchForMovies(query: String) { fun searchForMovies(query: String) {
movieResults.value = createPagingSource(viewModelScope) { service.searchMovies(query, it) } movieResults.value = createPagingSource { service.searchMovies(query, it) }
} }
fun searchForTv(query: String) { fun searchForTv(query: String) {
tvResults.value = createPagingSource(viewModelScope) { service.searchTv(query, it) } tvResults.value = createPagingSource { service.searchTv(query, it) }
} }
fun searchForPeople(query: String) { fun searchForPeople(query: String) {
peopleResults.value = createPagingSource(viewModelScope) { service.searchPeople(query, it) } peopleResults.value = createPagingSource { service.searchPeople(query, it) }
} }
fun searchMulti(query: String) { fun searchMulti(query: String) {
multiResults.value = createPagingSource(viewModelScope) { service.searchMulti(query, it) } multiResults.value = createPagingSource { service.searchMulti(query, it) }
} }
private fun <T: Searchable> createPagingSource(viewModelScope: CoroutineScope, provideResults: SearchResultProvider<T>): Flow<PagingData<T>> { private fun <T: Searchable> createPagingSource(provideResults: SearchResultProvider<T>): Flow<PagingData<T>> {
return Pager(PagingConfig(pageSize = 1)) { SearchPagingSource(provideResults) }.flow.cachedIn(viewModelScope) return createPagingFlow(
fetcher = { provideResults(it) },
processor = { it.results }
)
} }
} }

View File

@@ -2,6 +2,6 @@ package com.owenlejeune.tvtime.ui.viewmodel
object ViewModelConstants { object ViewModelConstants {
const val PAGING_SIZE = 6 const val PAGING_SIZE = 3
} }

View File

@@ -2,22 +2,17 @@ package com.owenlejeune.tvtime.utils
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AccountList
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.utils.types.MediaViewType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -133,76 +128,10 @@ object SessionManager: KoinComponent {
abstract class Session(val sessionId: String, val isAuthorized: Boolean, val accessToken: String = "", val accountId: String = "") { abstract class Session(val sessionId: String, val isAuthorized: Boolean, val accessToken: String = "", val accountId: String = "") {
val ratedMovies = mutableStateListOf<RatedMovie>()
val ratedTvShows = mutableStateListOf<RatedTv>()
val ratedTvEpisodes = mutableStateListOf<RatedEpisode>()
val accountDetails = mutableStateOf<AccountDetails?>(null) val accountDetails = mutableStateOf<AccountDetails?>(null)
val accountLists = mutableStateListOf<AccountList>()
val favoriteMovies = mutableStateListOf<FavoriteMovie>()
val favoriteTvShows = mutableStateListOf<FavoriteTvSeries>()
val movieWatchlist = mutableStateListOf<WatchlistMovie>()
val tvWatchlist = mutableStateListOf<WatchlistTvSeries>()
fun hasRatedMovie(id: Int): Boolean {
return ratedMovies.map { it.id }.contains(id)
}
fun hasRatedTvShow(id: Int): Boolean {
return ratedTvShows.map { it.id }.contains(id)
}
fun hasRatedTvEpisode(id: Int): Boolean {
return ratedTvEpisodes.map { it.id }.contains(id)
}
fun getRatingForId(id: Int, type: MediaViewType): Float? {
return when(type) {
MediaViewType.MOVIE -> ratedMovies.firstOrNull { it.id == id }?.rating
MediaViewType.TV -> ratedTvShows.firstOrNull { it.id == id }?.rating
MediaViewType.EPISODE -> ratedTvEpisodes.firstOrNull { it.id == id }?.rating
else -> null
}
}
fun hasFavoritedMovie(id: Int): Boolean {
return favoriteMovies.map { it.id }.contains(id)
}
fun hasFavoritedTvShow(id: Int): Boolean {
return favoriteTvShows.map { it.id }.contains(id)
}
fun hasWatchlistedMovie(id: Int): Boolean {
return movieWatchlist.map { it.id }.contains(id)
}
fun hasWatchlistedTvShow(id: Int): Boolean {
return tvWatchlist.map { it.id }.contains(id)
}
abstract suspend fun initialize() abstract suspend fun initialize()
abstract suspend fun refresh(changed: Array<Changed> = Changed.All)
enum class Changed {
AccountDetails,
Lists,
RatedMovies,
RatedTv,
RatedEpisodes,
FavoriteMovies,
FavoriteTv,
WatchlistMovies,
WatchlistTv;
companion object {
val All get() = values()
val Rated get() = arrayOf(RatedMovies, RatedTv, RatedEpisodes)
val Favorites get() = arrayOf(FavoriteMovies, FavoriteTv)
val Watchlist get() = arrayOf(WatchlistMovies, WatchlistTv)
val List get() = arrayOf(Lists)
}
}
} }
private class InProgressSession(requestToken: String): Session(requestToken, false) { private class InProgressSession(requestToken: String): Session(requestToken, false) {
@@ -210,10 +139,6 @@ object SessionManager: KoinComponent {
// do nothing // do nothing
} }
override suspend fun refresh(changed: Array<Changed>) {
// do nothing
}
} }
private class AuthorizedSession( private class AuthorizedSession(
@@ -222,122 +147,11 @@ object SessionManager: KoinComponent {
accountId: String = "" accountId: String = ""
): Session(sessionId, true, accessToken, accountId) { ): Session(sessionId, true, accessToken, accountId) {
private val service: AccountService by inject() private val service: AccountService by inject()
private val serviceV4: AccountV4Service by inject()
override suspend fun initialize() { override suspend fun initialize() {
refresh() val response = service.getAccountDetails()
} if (response.isSuccessful) {
accountDetails.value = response.body()
override suspend fun refresh(changed: Array<Changed>) {
if (changed.contains(Changed.AccountDetails)) {
val response = service.getAccountDetails()
if (response.isSuccessful) {
accountDetails.value = response.body()
accountDetails.value?.let {
refreshWithAccountId(it.id, changed)
}
}
} else if (accountDetails.value != null) {
refreshWithAccountId(accountDetails.value!!.id, changed)
}
}
private suspend fun refreshWithAccountId(accountId: Int, changed: Array<Changed> = Changed.All) {
if (changed.contains(Changed.Lists)) {
serviceV4.getLists(preferences.authorizedSessionValues?.accountId ?: "").apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
accountLists.clear()
accountLists.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.FavoriteMovies)) {
service.getFavoriteMovies(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
favoriteMovies.clear()
favoriteMovies.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.FavoriteTv)) {
service.getFavoriteTvShows(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
favoriteTvShows.clear()
favoriteTvShows.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.RatedMovies)) {
service.getRatedMovies(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
ratedMovies.clear()
ratedMovies.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.RatedTv)) {
service.getRatedTvShows(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
ratedTvShows.clear()
ratedTvShows.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.RatedEpisodes)) {
service.getRatedTvEpisodes(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
ratedTvEpisodes.clear()
ratedTvEpisodes.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.WatchlistMovies)) {
service.getMovieWatchlist(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
movieWatchlist.clear()
movieWatchlist.addAll(it)
}
}
}
}
}
if (changed.contains(Changed.WatchlistTv)) {
service.getTvWatchlist(accountId).apply {
if (isSuccessful) {
withContext(Dispatchers.Main) {
body()?.results?.let {
tvWatchlist.clear()
tvWatchlist.addAll(it)
}
}
}
}
} }
} }
} }

View File

@@ -94,6 +94,7 @@
<string name="search_icon_content_descriptor">Search Icon</string> <string name="search_icon_content_descriptor">Search Icon</string>
<string name="rating_dialog_title">Add a Rating</string> <string name="rating_dialog_title">Add a Rating</string>
<string name="my_rating_dialog_title">My Rating</string>
<string name="rating_dialog_confirm">Submit rating</string> <string name="rating_dialog_confirm">Submit rating</string>
<string name="rating_dialog_delete">Delete rating</string> <string name="rating_dialog_delete">Delete rating</string>