mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-13 23:32:46 -05:00
refactor account tabs to use paging
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,54 +9,12 @@ interface AccountApi {
|
||||
@GET("account")
|
||||
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")
|
||||
suspend fun markAsFavorite(
|
||||
@Path("id") id: Int,
|
||||
@Body body: MarkAsFavoriteBody
|
||||
): 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")
|
||||
suspend fun addToWatchlist(
|
||||
@Path("id") id: Int,
|
||||
|
||||
@@ -2,18 +2,8 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||
|
||||
import android.util.Log
|
||||
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.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.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.inject
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,7 +106,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
} else {
|
||||
Log.d(TAG, "Issue getting account states: $response")
|
||||
}
|
||||
// movieService.getAccountStates(id) storedIn { accountStates[id] = it }
|
||||
}
|
||||
|
||||
suspend fun getReleaseDates(id: Int) {
|
||||
@@ -118,7 +117,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
val response = movieService.postMovieRatingAsUser(id, session.sessionId, ratingBody)
|
||||
if (response.isSuccessful) {
|
||||
Log.d(TAG, "Successfully rated")
|
||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
|
||||
getAccountStates(id)
|
||||
} else {
|
||||
Log.w(TAG, "Issue posting rating")
|
||||
@@ -130,7 +128,6 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
val response = movieService.deleteMovieReviewAsUser(id, session.sessionId)
|
||||
if (response.isSuccessful) {
|
||||
Log.d(TAG, "Successfully deleted rated")
|
||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
|
||||
getAccountStates(id)
|
||||
} else {
|
||||
Log.w(TAG, "Issue deleting rating")
|
||||
@@ -158,41 +155,3 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,41 +58,3 @@ class SearchService: KoinComponent {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,7 +122,6 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
val response = service.postTvRatingAsUser(id, session.sessionId, ratingBody)
|
||||
if (response.isSuccessful) {
|
||||
Log.d(TAG, "Successfully posted rating")
|
||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
|
||||
} else {
|
||||
Log.w(TAG, "Issue posting rating")
|
||||
}
|
||||
@@ -133,13 +132,11 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
||||
val response = service.deleteTvReviewAsUser(id, session.sessionId)
|
||||
if (response.isSuccessful) {
|
||||
Log.d(TAG, "Successfully deleted rating")
|
||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.Rated)
|
||||
} else {
|
||||
Log.w(TAG, "Issue deleting rating")
|
||||
}
|
||||
}
|
||||
|
||||
//todo - turn this into paging
|
||||
override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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.FavoriteTvSeries
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedTv
|
||||
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.v4.model.AccountList
|
||||
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.V4RatedTv
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
|
||||
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.http.GET
|
||||
import retrofit2.http.Path
|
||||
@@ -24,23 +26,17 @@ interface AccountV4Api {
|
||||
@GET("account/{account_id}/tv/favorites")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
suspend fun getRecommendedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<AccountResponse<RecommendedMovie>>
|
||||
|
||||
@@ -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.api.v3.model.FavoriteMovie
|
||||
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.v4.model.RecommendedTv
|
||||
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.v4.model.AccountList
|
||||
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.V4RatedTv
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RatedMovie
|
||||
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
|
||||
|
||||
class AccountV4Service {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
suspend fun getMovieRecommendations(accountId: String, page: Int = 1): Response<AccountResponse<FavoriteMovie>> {
|
||||
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>> {
|
||||
suspend fun getMovieWatchlist(accountId: String, page: Int): Response<AccountResponse<WatchlistMovie>> {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import com.owenlejeune.tvtime.BuildConfig
|
||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AddToListBody
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.CreateListBody
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsBody
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListUpdateBody
|
||||
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.inject
|
||||
import retrofit2.Response
|
||||
|
||||
class ListV4Service: KoinComponent {
|
||||
|
||||
@@ -47,7 +47,6 @@ class ListV4Service: KoinComponent {
|
||||
suspend fun deleteListItems(listId: Int, body: DeleteListItemsBody) {
|
||||
val response = service.deleteListItems(listId, body)
|
||||
if (response.isSuccessful) {
|
||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.List)
|
||||
getList(listId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
abstract class V4RatedMedia(
|
||||
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: AccountRating,
|
||||
@SerializedName("account_rating") val rating: AccountRating,
|
||||
@SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String,
|
||||
@SerializedName("backdrop_path") val backdropPath: String?,
|
||||
@SerializedName("genre_ids") val genreIds: List<Int>,
|
||||
@@ -25,7 +25,7 @@ abstract class V4RatedMedia(
|
||||
}
|
||||
|
||||
inner class AccountRating(
|
||||
@SerializedName("value") val rating: Int,
|
||||
@SerializedName("value") val value: Float,
|
||||
@SerializedName("created_at") val createdAt: String
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ class RatedMovie(
|
||||
name: String,
|
||||
voteAverage: Float,
|
||||
voteCount: Int,
|
||||
rating: Float,
|
||||
rating: AccountRating,
|
||||
backdropPath: String?,
|
||||
genreIds: List<Int>,
|
||||
originalLanguage: String,
|
||||
@@ -17,8 +17,8 @@ class RatedMovie(
|
||||
popularity: Float,
|
||||
releaseDate: String,
|
||||
@SerializedName("adult") val isAdult: Boolean,
|
||||
@SerializedName("video") val video: Boolean,
|
||||
): RatedTopLevelMedia(
|
||||
@SerializedName("video") val video: Boolean
|
||||
): RatedMedia(
|
||||
RatedType.MOVIE, id, overview, name, voteAverage, voteCount, rating, releaseDate,
|
||||
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ class RatedTv(
|
||||
name: String,
|
||||
voteAverage: Float,
|
||||
voteCount: Int,
|
||||
rating: Float,
|
||||
rating: AccountRating,
|
||||
backdropPath: String?,
|
||||
genreIds: List<Int>,
|
||||
originalLanguage: String,
|
||||
@@ -17,7 +17,7 @@ class RatedTv(
|
||||
popularity: Float,
|
||||
releaseDate: String,
|
||||
@SerializedName("origin_country") val originCountry: List<String>,
|
||||
): RatedTopLevelMedia(
|
||||
): RatedMedia(
|
||||
RatedType.SERIES, id, overview, name, voteAverage, voteCount, rating, releaseDate,
|
||||
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -74,9 +74,7 @@ fun ActionsView(
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.wrapContentSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (actions.contains(Actions.RATE)) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.owenlejeune.tvtime.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
@@ -34,7 +33,13 @@ fun RatingDialog(
|
||||
AlertDialog(
|
||||
modifier = Modifier.wrapContentHeight(),
|
||||
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 = {
|
||||
Button(
|
||||
modifier = Modifier.height(40.dp),
|
||||
|
||||
@@ -5,17 +5,14 @@ import androidx.navigation.NavHostController
|
||||
import com.owenlejeune.tvtime.R
|
||||
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.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.WatchlistTvSeries
|
||||
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.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.ui.screens.RecommendedAccountTabContent
|
||||
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 org.koin.core.component.inject
|
||||
import kotlin.reflect.KClass
|
||||
@@ -26,8 +23,8 @@ sealed class AccountTabNavItem(
|
||||
noContentStringRes: Int,
|
||||
val mediaType: MediaViewType,
|
||||
val screen: AccountNavComposableFun,
|
||||
val listFetchFun: ListFetchFun,
|
||||
val listType: KClass<*>,
|
||||
val type: AccountTabType,
|
||||
val contentType: KClass<*>,
|
||||
val ordinal: Int
|
||||
): TabNavItem(route) {
|
||||
private val resourceUtils: ResourceUtils by inject()
|
||||
@@ -35,10 +32,18 @@ sealed class AccountTabNavItem(
|
||||
override val name = resourceUtils.getString(stringRes)
|
||||
val noContentText = resourceUtils.getString(noContentStringRes)
|
||||
|
||||
enum class AccountTabType {
|
||||
RATED,
|
||||
FAVORITE,
|
||||
WATCHLIST,
|
||||
LIST,
|
||||
RECOMMENDED
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AuthorizedItems
|
||||
get() = listOf(
|
||||
RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows,
|
||||
RatedMovies, RatedTvShows, /*RatedTvEpisodes, */FavoriteMovies, FavoriteTvShows,
|
||||
MovieWatchlist, TvWatchlist, UserLists, RecommendedMovies, RecommendedTv
|
||||
).filter { it.ordinal > -1 }.sortedBy { it.ordinal }
|
||||
}
|
||||
@@ -49,7 +54,7 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_rated_movies,
|
||||
MediaViewType.MOVIE,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.ratedMovies ?: emptyList() },
|
||||
AccountTabType.RATED,
|
||||
RatedMovie::class,
|
||||
0
|
||||
)
|
||||
@@ -59,27 +64,27 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_rated_tv,
|
||||
MediaViewType.TV,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.ratedTvShows ?: emptyList() },
|
||||
AccountTabType.RATED,
|
||||
RatedTv::class,
|
||||
1
|
||||
)
|
||||
object RatedTvEpisodes: AccountTabNavItem(
|
||||
R.string.nav_rated_episodes_title,
|
||||
"rated_episodes_route",
|
||||
R.string.no_rated_episodes,
|
||||
MediaViewType.EPISODE,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.ratedTvEpisodes ?: emptyList() },
|
||||
RatedEpisode::class,
|
||||
-1 //2
|
||||
)
|
||||
// object RatedTvEpisodes: AccountTabNavItem(
|
||||
// R.string.nav_rated_episodes_title,
|
||||
// "rated_episodes_route",
|
||||
// R.string.no_rated_episodes,
|
||||
// MediaViewType.EPISODE,
|
||||
// screenContent,
|
||||
// AccountTabType.RATED,
|
||||
// RatedEpisode::class,
|
||||
// -1 //2
|
||||
// )
|
||||
object FavoriteMovies: AccountTabNavItem(
|
||||
R.string.nav_favorite_movies_title,
|
||||
"favorite_movies_route",
|
||||
R.string.no_favorite_movies,
|
||||
MediaViewType.MOVIE,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.favoriteMovies ?: emptyList() },
|
||||
AccountTabType.FAVORITE,
|
||||
FavoriteMovie::class,
|
||||
3
|
||||
)
|
||||
@@ -89,7 +94,7 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_favorite_tv,
|
||||
MediaViewType.TV,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.favoriteTvShows ?: emptyList() },
|
||||
AccountTabType.FAVORITE,
|
||||
FavoriteTvSeries::class,
|
||||
4
|
||||
)
|
||||
@@ -99,7 +104,7 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_watchlist_movies,
|
||||
MediaViewType.MOVIE,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.movieWatchlist ?: emptyList() },
|
||||
AccountTabType.WATCHLIST,
|
||||
WatchlistMovie::class,
|
||||
5
|
||||
)
|
||||
@@ -109,7 +114,7 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_watchlist_tv,
|
||||
MediaViewType.TV,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.tvWatchlist ?: emptyList() },
|
||||
AccountTabType.WATCHLIST,
|
||||
WatchlistTvSeries::class,
|
||||
6
|
||||
)
|
||||
@@ -120,7 +125,7 @@ sealed class AccountTabNavItem(
|
||||
R.string.no_lists,
|
||||
MediaViewType.LIST,
|
||||
screenContent,
|
||||
{ SessionManager.currentSession.value?.accountLists ?: emptyList() },
|
||||
AccountTabType.LIST,
|
||||
AccountList::class,
|
||||
7
|
||||
)
|
||||
@@ -130,8 +135,8 @@ sealed class AccountTabNavItem(
|
||||
"recommended_movies_route",
|
||||
R.string.no_recommended_movies,
|
||||
MediaViewType.MOVIE,
|
||||
recommendedScreenContent,
|
||||
{ emptyList() },
|
||||
screenContent,
|
||||
AccountTabType.RECOMMENDED,
|
||||
AccountList::class,
|
||||
8
|
||||
)
|
||||
@@ -141,31 +146,21 @@ sealed class AccountTabNavItem(
|
||||
"recommended_tv_route",
|
||||
R.string.no_recommended_tv,
|
||||
MediaViewType.TV,
|
||||
recommendedScreenContent,
|
||||
{ emptyList() },
|
||||
screenContent,
|
||||
AccountTabType.RECOMMENDED,
|
||||
AccountList::class,
|
||||
9
|
||||
)
|
||||
}
|
||||
|
||||
private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, listFetchFun, clazz ->
|
||||
private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, atType, clazz ->
|
||||
AccountTabContent(
|
||||
noContentText = noContentText,
|
||||
appNavController = appNavController,
|
||||
mediaViewType = mediaViewType,
|
||||
listFetchFun = listFetchFun,
|
||||
accountTabType = atType,
|
||||
clazz = clazz
|
||||
)
|
||||
}
|
||||
|
||||
private val recommendedScreenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, _, _ ->
|
||||
RecommendedAccountTabContent(
|
||||
noContentText = noContentText,
|
||||
appNavController = appNavController,
|
||||
mediaViewType = mediaViewType,
|
||||
)
|
||||
}
|
||||
|
||||
typealias ListFetchFun = () -> List<Any>
|
||||
|
||||
typealias AccountNavComposableFun = @Composable (String, NavHostController, MediaViewType, ListFetchFun, KClass<*>) -> Unit
|
||||
typealias AccountNavComposableFun = @Composable (String, NavHostController, MediaViewType, AccountTabNavItem.AccountTabType, KClass<*>) -> Unit
|
||||
@@ -5,11 +5,11 @@ import androidx.navigation.NavHostController
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
|
||||
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.utils.ResourceUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import com.owenlejeune.tvtime.utils.types.TabNavItem
|
||||
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
|
||||
import org.koin.core.component.inject
|
||||
import retrofit2.Response
|
||||
|
||||
@@ -17,17 +17,31 @@ sealed class MediaTabNavItem(
|
||||
stringRes: Int,
|
||||
route: String,
|
||||
val screen: MediaNavComposableFun,
|
||||
val movieViewModel: MediaTabViewModel?,
|
||||
val tvViewModel: MediaTabViewModel?
|
||||
val type: Type
|
||||
): TabNavItem(route) {
|
||||
private val resourceUtils: ResourceUtils by inject()
|
||||
|
||||
override val name = resourceUtils.getString(stringRes)
|
||||
|
||||
enum class Type {
|
||||
POPULAR,
|
||||
NOW_PLAYING,
|
||||
UPCOMING,
|
||||
TOP_RATED
|
||||
}
|
||||
|
||||
companion object {
|
||||
val MovieItems = listOf(NowPlaying, Popular, Upcoming, 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)
|
||||
|
||||
fun getByRoute(route: String?): MediaTabNavItem? {
|
||||
@@ -39,43 +53,37 @@ sealed class MediaTabNavItem(
|
||||
stringRes = R.string.nav_popular_title,
|
||||
route = "popular_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = MediaTabViewModel.PopularMoviesVM,
|
||||
tvViewModel = MediaTabViewModel.PopularTvVM
|
||||
type = Type.POPULAR
|
||||
)
|
||||
object TopRated: MediaTabNavItem(
|
||||
stringRes = R.string.nav_top_rated_title,
|
||||
route = "top_rated_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = MediaTabViewModel.TopRatedMoviesVM,
|
||||
tvViewModel = MediaTabViewModel.TopRatedTvVM
|
||||
type = Type.TOP_RATED
|
||||
)
|
||||
object NowPlaying: MediaTabNavItem(
|
||||
stringRes = R.string.nav_now_playing_title,
|
||||
route = "now_playing_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = MediaTabViewModel.NowPlayingMoviesVM,
|
||||
tvViewModel = null
|
||||
type = Type.NOW_PLAYING
|
||||
)
|
||||
object Upcoming: MediaTabNavItem(
|
||||
stringRes = R.string.nav_upcoming_title,
|
||||
route = "upcoming_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = MediaTabViewModel.UpcomingMoviesVM,
|
||||
tvViewModel = null
|
||||
type = Type.UPCOMING
|
||||
)
|
||||
object AiringToday: MediaTabNavItem(
|
||||
stringRes = R.string.nav_tv_airing_today_title,
|
||||
route = "airing_today_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = null,
|
||||
tvViewModel = MediaTabViewModel.AiringTodayTvVM
|
||||
type = Type.NOW_PLAYING
|
||||
)
|
||||
object OnTheAir: MediaTabNavItem(
|
||||
stringRes = R.string.nav_tv_on_the_air,
|
||||
route = "on_the_air_route",
|
||||
screen = screenContent,
|
||||
movieViewModel = null,
|
||||
tvViewModel = MediaTabViewModel.OnTheAirTvVM
|
||||
type = Type.UPCOMING
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
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.api.tmdb.api.v3.model.*
|
||||
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.ui.components.AccountIcon
|
||||
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.navigation.AccountTabNavItem
|
||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.RecommendedMediaViewModel
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
|
||||
import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
@@ -177,12 +182,13 @@ fun <T: Any> AccountTabContent(
|
||||
noContentText: String,
|
||||
appNavController: NavHostController,
|
||||
mediaViewType: MediaViewType,
|
||||
listFetchFun: ListFetchFun,
|
||||
accountTabType: AccountTabNavItem.AccountTabType,
|
||||
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 {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
@@ -195,6 +201,15 @@ fun <T: Any> AccountTabContent(
|
||||
)
|
||||
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 {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
@@ -202,10 +217,10 @@ fun <T: Any> AccountTabContent(
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(contentItems.size) { i ->
|
||||
lazyPagingItems(contentItems) { i ->
|
||||
when (clazz) {
|
||||
RatedTv::class, RatedMovie::class -> {
|
||||
val item = contentItems[i] as RatedTopLevelMedia
|
||||
val item = i as RatedMedia
|
||||
MediaItemRow(
|
||||
appNavController = appNavController,
|
||||
mediaViewType = mediaViewType,
|
||||
@@ -214,24 +229,12 @@ fun <T: Any> AccountTabContent(
|
||||
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath),
|
||||
name = item.name,
|
||||
date = item.releaseDate,
|
||||
rating = item.rating,
|
||||
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,
|
||||
rating = item.rating.value,
|
||||
description = item.overview
|
||||
)
|
||||
}
|
||||
FavoriteMovie::class, FavoriteTvSeries::class -> {
|
||||
val item = contentItems[i] as FavoriteMedia
|
||||
val item = i as FavoriteMedia
|
||||
MediaItemRow(
|
||||
appNavController = appNavController,
|
||||
mediaViewType = mediaViewType,
|
||||
@@ -244,7 +247,7 @@ fun <T: Any> AccountTabContent(
|
||||
)
|
||||
}
|
||||
WatchlistMovie::class, WatchlistTvSeries::class -> {
|
||||
val item = contentItems[i] as WatchlistMedia
|
||||
val item = i as WatchlistMedia
|
||||
MediaItemRow(
|
||||
appNavController = appNavController,
|
||||
mediaViewType = mediaViewType,
|
||||
@@ -257,7 +260,7 @@ fun <T: Any> AccountTabContent(
|
||||
)
|
||||
}
|
||||
AccountList::class -> {
|
||||
val item = contentItems[i] as AccountList
|
||||
val item = i as AccountList
|
||||
MediaItemRow(
|
||||
appNavController = appNavController,
|
||||
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
|
||||
private fun MediaItemRow(
|
||||
appNavController: NavHostController,
|
||||
@@ -345,6 +311,6 @@ fun AccountTabs(
|
||||
appNavController: NavHostController
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,6 @@ fun ListDetailScreen(
|
||||
val listMap = remember { accountViewModel.listMap }
|
||||
val parentList = listMap[itemId]
|
||||
|
||||
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||
val scrollBehavior = remember(decayAnimationSpec) {
|
||||
@@ -133,9 +132,7 @@ fun ListDetailScreen(
|
||||
val selectedSortOrder = remember { mutableStateOf(mediaList.sortBy) }
|
||||
ListHeader(
|
||||
list = mediaList,
|
||||
selectedSortOrder = selectedSortOrder,
|
||||
service = service,
|
||||
parentList = parentList
|
||||
selectedSortOrder = selectedSortOrder
|
||||
)
|
||||
|
||||
val sortedResults = selectedSortOrder.value.sort(mediaList.results)
|
||||
@@ -155,9 +152,7 @@ fun ListDetailScreen(
|
||||
@Composable
|
||||
private fun ListHeader(
|
||||
list: MediaList,
|
||||
selectedSortOrder: MutableState<SortOrder>,
|
||||
service: ListV4Service,
|
||||
parentList: MediaList?
|
||||
selectedSortOrder: MutableState<SortOrder>
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ private fun MediaViewContent(
|
||||
|
||||
val currentSession = remember { SessionManager.currentSession }
|
||||
currentSession.value?.let {
|
||||
ActionsView(itemId = itemId, type = type)
|
||||
ActionsView(itemId = itemId, type = type, modifier = Modifier.padding(start = 20.dp))
|
||||
}
|
||||
|
||||
if (type == MediaViewType.MOVIE) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.tabs
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
@@ -16,11 +14,11 @@ import com.google.accompanist.pager.rememberPagerState
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
|
||||
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.MediaTabNavItem
|
||||
import com.owenlejeune.tvtime.ui.components.Tabs
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@@ -44,11 +42,7 @@ fun MediaTab(
|
||||
mediaType = mediaType
|
||||
)
|
||||
|
||||
val tabs = when (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 tabs = MediaTabNavItem.itemsForType(type = mediaType)
|
||||
val pagerState = rememberPagerState()
|
||||
Tabs(tabs = tabs, pagerState = pagerState)
|
||||
MediaTabs(
|
||||
@@ -61,13 +55,13 @@ fun MediaTab(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MediaTabContent(appNavController: NavHostController, mediaType: MediaViewType, mediaTabItem: MediaTabNavItem) {
|
||||
val viewModel: MediaTabViewModel? = when(mediaType) {
|
||||
MediaViewType.MOVIE -> mediaTabItem.movieViewModel
|
||||
MediaViewType.TV -> mediaTabItem.tvViewModel
|
||||
else -> throw IllegalArgumentException("Media type given: ${mediaType}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV") // shouldn't happen
|
||||
}
|
||||
val mediaListItems = viewModel?.mediaItems?.collectAsLazyPagingItems()
|
||||
fun MediaTabContent(
|
||||
appNavController: NavHostController,
|
||||
mediaType: MediaViewType,
|
||||
mediaTabItem: MediaTabNavItem
|
||||
) {
|
||||
val viewModel = viewModel<MainViewModel>()
|
||||
val mediaListItems = viewModel.produceFlowFor(mediaType, mediaTabItem.type).collectAsLazyPagingItems()
|
||||
|
||||
PagingPosterGrid(
|
||||
lazyPagingItems = mediaListItems,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.tabs
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.navigation.AppNavItem
|
||||
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
|
||||
|
||||
@Composable
|
||||
@@ -30,7 +28,8 @@ fun PeopleTab(
|
||||
mediaType = MediaViewType.PERSON
|
||||
)
|
||||
|
||||
val peopleList = PeopleTabViewModel().popularPeople.collectAsLazyPagingItems()
|
||||
val mainViewModel = viewModel<MainViewModel>()
|
||||
val peopleList = mainViewModel.popularPeople.collectAsLazyPagingItems()
|
||||
|
||||
PagingPeoplePosterGrid(
|
||||
lazyPagingItems = peopleList,
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.owenlejeune.tvtime.ui.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.model.MarkAsFavoriteBody
|
||||
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.model.DeleteListItemsBody
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsItem
|
||||
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.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.inject
|
||||
|
||||
@@ -17,9 +23,94 @@ class AccountViewModel: ViewModel(), KoinComponent {
|
||||
|
||||
private val listService: ListV4Service 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 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) {
|
||||
listService.getList(listId = listId)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
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.createPagingFlow
|
||||
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.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.model.AccountStates
|
||||
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.Video
|
||||
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.ViewableMediaTypeException
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -48,6 +44,23 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val similarMovies = movieService.similar
|
||||
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 tvImages = tvService.images
|
||||
val tvCast = tvService.cast
|
||||
@@ -62,12 +75,34 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
val similarTv = tvService.similar
|
||||
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 peopleCastMap = peopleService.castMap
|
||||
val peopleCrewMap = peopleService.crewMap
|
||||
val peopleImagesMap = peopleService.imagesMap
|
||||
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 {
|
||||
return when (type) {
|
||||
MediaViewType.MOVIE -> movies()
|
||||
@@ -120,6 +155,28 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
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) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> movieService.getById(id)
|
||||
@@ -215,14 +272,16 @@ class MainViewModel: ViewModel(), KoinComponent {
|
||||
fun getSimilar(id: Int, type: MediaViewType) {
|
||||
when (type) {
|
||||
MediaViewType.MOVIE -> {
|
||||
similarMovies[id] = Pager(PagingConfig(pageSize = 1)) {
|
||||
SimilarMoviesSource(id)
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
similarMovies[id] = createPagingFlow(
|
||||
fetcher = { p -> movieService.getSimilar(id, p) },
|
||||
processor = { it.results }
|
||||
)
|
||||
}
|
||||
MediaViewType.TV -> {
|
||||
similarTv[id] = Pager(PagingConfig(pageSize = 1)) {
|
||||
SimilarTvSource(id)
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
similarTv[id] = createPagingFlow(
|
||||
fetcher = { p -> tvService.getSimilar(id, p) },
|
||||
processor = { it.results }
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -1,22 +1,12 @@
|
||||
package com.owenlejeune.tvtime.ui.viewmodel
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.SearchPagingSource
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.createPagingFlow
|
||||
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.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.SortableSearchResult
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@@ -48,23 +38,26 @@ class SearchViewModel: ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
fun searchForMovies(query: String) {
|
||||
movieResults.value = createPagingSource(viewModelScope) { service.searchMovies(query, it) }
|
||||
movieResults.value = createPagingSource { service.searchMovies(query, it) }
|
||||
}
|
||||
|
||||
fun searchForTv(query: String) {
|
||||
tvResults.value = createPagingSource(viewModelScope) { service.searchTv(query, it) }
|
||||
tvResults.value = createPagingSource { service.searchTv(query, it) }
|
||||
}
|
||||
|
||||
fun searchForPeople(query: String) {
|
||||
peopleResults.value = createPagingSource(viewModelScope) { service.searchPeople(query, it) }
|
||||
peopleResults.value = createPagingSource { service.searchPeople(query, it) }
|
||||
}
|
||||
|
||||
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>> {
|
||||
return Pager(PagingConfig(pageSize = 1)) { SearchPagingSource(provideResults) }.flow.cachedIn(viewModelScope)
|
||||
private fun <T: Searchable> createPagingSource(provideResults: SearchResultProvider<T>): Flow<PagingData<T>> {
|
||||
return createPagingFlow(
|
||||
fetcher = { provideResults(it) },
|
||||
processor = { it.results }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,6 @@ package com.owenlejeune.tvtime.ui.viewmodel
|
||||
|
||||
object ViewModelConstants {
|
||||
|
||||
const val PAGING_SIZE = 6
|
||||
const val PAGING_SIZE = 3
|
||||
|
||||
}
|
||||
@@ -2,22 +2,17 @@ package com.owenlejeune.tvtime.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.google.gson.annotations.SerializedName
|
||||
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.AuthenticationService
|
||||
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.model.AuthAccessBody
|
||||
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.AccountList
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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 = "") {
|
||||
|
||||
val ratedMovies = mutableStateListOf<RatedMovie>()
|
||||
val ratedTvShows = mutableStateListOf<RatedTv>()
|
||||
val ratedTvEpisodes = mutableStateListOf<RatedEpisode>()
|
||||
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 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) {
|
||||
@@ -210,10 +139,6 @@ object SessionManager: KoinComponent {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override suspend fun refresh(changed: Array<Changed>) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class AuthorizedSession(
|
||||
@@ -222,122 +147,11 @@ object SessionManager: KoinComponent {
|
||||
accountId: String = ""
|
||||
): Session(sessionId, true, accessToken, accountId) {
|
||||
private val service: AccountService by inject()
|
||||
private val serviceV4: AccountV4Service by inject()
|
||||
|
||||
override suspend fun initialize() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val response = service.getAccountDetails()
|
||||
if (response.isSuccessful) {
|
||||
accountDetails.value = response.body()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
<string name="search_icon_content_descriptor">Search Icon</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_delete">Delete rating</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user