mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-19 10:11:13 -05:00
refactor data storage model to viewmodels
This commit is contained in:
11
app/src/main/java/com/owenlejeune/tvtime/api/ServiceUtils.kt
Normal file
11
app/src/main/java/com/owenlejeune/tvtime/api/ServiceUtils.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.owenlejeune.tvtime.api
|
||||||
|
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
|
infix fun <T> Response<T>.storedIn(body: (T) -> Unit) {
|
||||||
|
if (isSuccessful) {
|
||||||
|
body()?.let {
|
||||||
|
body(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,13 @@ import androidx.compose.ui.text.intl.Locale
|
|||||||
import com.owenlejeune.tvtime.BuildConfig
|
import com.owenlejeune.tvtime.BuildConfig
|
||||||
import com.owenlejeune.tvtime.api.Client
|
import com.owenlejeune.tvtime.api.Client
|
||||||
import com.owenlejeune.tvtime.api.QueryParam
|
import com.owenlejeune.tvtime.api.QueryParam
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.*
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.ConfigurationApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchApi
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvApi
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Api
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Api
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Api
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Api
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Api
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Api
|
||||||
@@ -53,10 +59,6 @@ class TmdbClient: KoinComponent {
|
|||||||
return clientV4.create(AuthenticationV4Api::class.java)
|
return clientV4.create(AuthenticationV4Api::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createGuestSessionService(): GuestSessionApi {
|
|
||||||
return client.create(GuestSessionApi::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createAccountService(): AccountApi {
|
fun createAccountService(): AccountApi {
|
||||||
return client.create(AccountApi::class.java)
|
return client.create(AccountApi::class.java)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,52 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import android.util.Log
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
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
|
import retrofit2.Response
|
||||||
|
|
||||||
class AccountService {
|
class AccountService: KoinComponent {
|
||||||
|
|
||||||
private val accountService by lazy { TmdbClient().createAccountService() }
|
private val TAG = "AccountService"
|
||||||
|
|
||||||
|
private val accountService: AccountApi by inject()
|
||||||
|
|
||||||
suspend fun getAccountDetails(): Response<AccountDetails> {
|
suspend fun getAccountDetails(): Response<AccountDetails> {
|
||||||
return accountService.getAccountDetails()
|
return accountService.getAccountDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun markAsFavorite(accountId: Int, body: MarkAsFavoriteBody) {
|
||||||
|
val response = accountService.markAsFavorite(accountId, body)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
Log.d(TAG, "Successfully marked as favourite")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue marking as favourite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addToWatchlist(accountId: Int, body: WatchlistBody) {
|
||||||
|
val response = accountService.addToWatchlist(accountId, body)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
Log.d(TAG, "Successfully added to watchlist")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue adding to watchlist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - replace these with account states API calls
|
||||||
suspend fun getFavoriteMovies(accountId: Int, page: Int = 1): Response<FavoriteMediaResponse<FavoriteMovie>> {
|
suspend fun getFavoriteMovies(accountId: Int, page: Int = 1): Response<FavoriteMediaResponse<FavoriteMovie>> {
|
||||||
return accountService.getFavoriteMovies(accountId, page)
|
return accountService.getFavoriteMovies(accountId, page)
|
||||||
}
|
}
|
||||||
@@ -20,10 +55,6 @@ class AccountService {
|
|||||||
return accountService.getFavoriteTvShows(accountId, page)
|
return accountService.getFavoriteTvShows(accountId, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsFavorite(accountId: Int, body: MarkAsFavoriteBody): Response<StatusResponse> {
|
|
||||||
return accountService.markAsFavorite(accountId, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getRatedMovies(accountId: Int, page: Int = 1): Response<RatedMediaResponse<RatedMovie>> {
|
suspend fun getRatedMovies(accountId: Int, page: Int = 1): Response<RatedMediaResponse<RatedMovie>> {
|
||||||
return accountService.getRatedMovies(accountId, page)
|
return accountService.getRatedMovies(accountId, page)
|
||||||
}
|
}
|
||||||
@@ -44,8 +75,4 @@ class AccountService {
|
|||||||
return accountService.getTvWatchlist(accountId, page)
|
return accountService.getTvWatchlist(accountId, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addToWatchlist(accountId: Int, body: WatchlistBody): Response<StatusResponse> {
|
|
||||||
return accountService.addToWatchlist(accountId, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationCountry
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationDetails
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationJob
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationLanguage
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationTimezone
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class ConfigurationService: KoinComponent {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ConfigurationService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val service: ConfigurationApi by inject()
|
||||||
|
|
||||||
|
val detailsConfiguration = mutableStateOf(ConfigurationDetails.Empty)
|
||||||
|
val countriesConfiguration = mutableStateListOf<ConfigurationCountry>()
|
||||||
|
val jobsConfiguration = mutableStateListOf<ConfigurationJob>()
|
||||||
|
val languagesConfiguration = mutableStateListOf<ConfigurationLanguage>()
|
||||||
|
val primaryTranslationsConfiguration = mutableStateListOf<String>()
|
||||||
|
val timezonesConfiguration = mutableStateListOf<ConfigurationTimezone>()
|
||||||
|
|
||||||
|
suspend fun getDetailsConfiguration() {
|
||||||
|
val response = service.getDetailsConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got details configuration: $it")
|
||||||
|
detailsConfiguration.value = it
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting details configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting details configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCountriesConfiguration() {
|
||||||
|
val response = service.getCountriesConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got countries configuration: $it")
|
||||||
|
countriesConfiguration.clear()
|
||||||
|
countriesConfiguration.addAll(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting countries configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting counties configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getJobsConfiguration() {
|
||||||
|
val response = service.getJobsConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got jobs configuration: $it")
|
||||||
|
jobsConfiguration.clear()
|
||||||
|
jobsConfiguration.addAll(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting jobs configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting jobs configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getLanguagesConfiguration() {
|
||||||
|
val response = service.getLanguagesConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got languages configuration: $it")
|
||||||
|
languagesConfiguration.clear()
|
||||||
|
languagesConfiguration.addAll(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting languages configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting languages configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getPrimaryTranslationsConfiguration() {
|
||||||
|
val response = service.getPrimaryTranslationsConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got translations configuration: $it")
|
||||||
|
primaryTranslationsConfiguration.clear()
|
||||||
|
primaryTranslationsConfiguration.addAll(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting translations configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting translations configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getTimezonesConfiguration() {
|
||||||
|
val response = service.getTimezonesConfiguration()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got timezone configuration: $it")
|
||||||
|
timezonesConfiguration.clear()
|
||||||
|
timezonesConfiguration.addAll(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting timezone configuration")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting timezone configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,26 +5,28 @@ import retrofit2.Response
|
|||||||
|
|
||||||
interface DetailService {
|
interface DetailService {
|
||||||
|
|
||||||
suspend fun getById(id: Int): Response<out DetailedItem>
|
suspend fun getById(id: Int)
|
||||||
|
|
||||||
suspend fun getImages(id: Int): Response<ImageCollection>
|
suspend fun getImages(id: Int)
|
||||||
|
|
||||||
suspend fun getCastAndCrew(id: Int): Response<CastAndCrew>
|
suspend fun getCastAndCrew(id: Int)
|
||||||
|
|
||||||
suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse>
|
suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse>
|
||||||
|
|
||||||
suspend fun getVideos(id: Int): Response<VideoResponse>
|
suspend fun getVideos(id: Int)
|
||||||
|
|
||||||
suspend fun getReviews(id: Int): Response<ReviewResponse>
|
suspend fun getReviews(id: Int)
|
||||||
|
|
||||||
suspend fun postRating(id: Int, ratingBody: RatingBody): Response<StatusResponse>
|
suspend fun postRating(id: Int, ratingBody: RatingBody)
|
||||||
|
|
||||||
suspend fun deleteRating(id: Int): Response<StatusResponse>
|
suspend fun deleteRating(id: Int)
|
||||||
|
|
||||||
suspend fun getKeywords(id: Int): Response<KeywordsResponse>
|
suspend fun getKeywords(id: Int)
|
||||||
|
|
||||||
suspend fun getWatchProviders(id: Int): Response<WatchProviderResponse>
|
suspend fun getWatchProviders(id: Int)
|
||||||
|
|
||||||
suspend fun getExternalIds(id: Int): Response<ExternalIds>
|
suspend fun getExternalIds(id: Int)
|
||||||
|
|
||||||
|
suspend fun getAccountStates(id: Int)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
|
||||||
|
|
||||||
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 retrofit2.Response
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Path
|
|
||||||
|
|
||||||
interface GuestSessionApi {
|
|
||||||
|
|
||||||
@GET("guest_session/{session_id}/rated/movies")
|
|
||||||
suspend fun getRatedMovies(@Path("session_id") sessionId: String): Response<RatedMediaResponse<RatedMovie>>
|
|
||||||
|
|
||||||
@GET("guest_session/{session_id}/rated/tv")
|
|
||||||
suspend fun getRatedTvShows(@Path("session_id") sessionId: String): Response<RatedMediaResponse<RatedTv>>
|
|
||||||
|
|
||||||
@GET("guest_session/{session_id}/rated/tv/episodes")
|
|
||||||
suspend fun getRatedTvEpisodes(@Path("session_id") sessionId: String): Response<RatedMediaResponse<RatedEpisode>>
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
|
||||||
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 retrofit2.Response
|
|
||||||
|
|
||||||
class GuestSessionService {
|
|
||||||
|
|
||||||
private val service by lazy { TmdbClient().createGuestSessionService() }
|
|
||||||
|
|
||||||
suspend fun getRatedMovies(sessionId: String): Response<RatedMediaResponse<RatedMovie>> {
|
|
||||||
return service.getRatedMovies(sessionId = sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getRatedTvShows(sessionId: String): Response<RatedMediaResponse<RatedTv>> {
|
|
||||||
return service.getRatedTvShows(sessionId = sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getRatedTvEpisodes(sessionId: String): Response<RatedMediaResponse<RatedEpisode>> {
|
|
||||||
return service.getRatedTvEpisodes(sessionId = sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -61,4 +61,7 @@ interface MoviesApi {
|
|||||||
@GET("movie/{id}/external_ids")
|
@GET("movie/{id}/external_ids")
|
||||||
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
|
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
|
||||||
|
|
||||||
|
@GET("movie/{id}/account_states")
|
||||||
|
suspend fun getAccountStates(@Path("id") id: Int, @Query("session_id") sessionId: String): Response<AccountStates>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,146 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import android.util.Log
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastAndCrew
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import com.owenlejeune.tvtime.api.storedIn
|
||||||
|
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.CrewMember
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedMovie
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Keyword
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieReleaseResults
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.MovieReleaseResults
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Review
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Searchable
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class MoviesService: KoinComponent, DetailService, HomePageService {
|
class MoviesService: KoinComponent, DetailService, HomePageService {
|
||||||
|
|
||||||
private val movieService by lazy { TmdbClient().createMovieService() }
|
companion object {
|
||||||
|
private const val TAG = "MovieService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val movieService: MoviesApi by inject()
|
||||||
|
|
||||||
|
val detailMovies = Collections.synchronizedMap(mutableStateMapOf<Int, DetailedMovie>())
|
||||||
|
val images = Collections.synchronizedMap(mutableStateMapOf<Int, ImageCollection>())
|
||||||
|
val cast = Collections.synchronizedMap(mutableStateMapOf<Int, List<CastMember>>())
|
||||||
|
val crew = Collections.synchronizedMap(mutableStateMapOf<Int, List<CrewMember>>())
|
||||||
|
val videos = Collections.synchronizedMap(mutableStateMapOf<Int, List<Video>>())
|
||||||
|
val reviews = Collections.synchronizedMap(mutableStateMapOf<Int, List<Review>>())
|
||||||
|
val keywords = Collections.synchronizedMap(mutableStateMapOf<Int, List<Keyword>>())
|
||||||
|
val watchProviders = Collections.synchronizedMap(mutableStateMapOf<Int, WatchProviders>())
|
||||||
|
val externalIds = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
||||||
|
val releaseDates = Collections.synchronizedMap(mutableStateMapOf<Int, List<MovieReleaseResults.ReleaseDateResult>>())
|
||||||
|
val similar = Collections.synchronizedMap(mutableStateMapOf<Int, Flow<PagingData<TmdbItem>>>())
|
||||||
|
val accountStates = Collections.synchronizedMap(mutableStateMapOf<Int, AccountStates>())
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun getById(id: Int) {
|
||||||
|
movieService.getMovieById(id) storedIn { detailMovies[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getImages(id: Int) {
|
||||||
|
movieService.getMovieImages(id) storedIn { images[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCastAndCrew(id: Int) {
|
||||||
|
movieService.getCastAndCrew(id) storedIn {
|
||||||
|
cast[id] = it.cast
|
||||||
|
crew[id] = it.crew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override suspend fun getVideos(id: Int) {
|
||||||
|
movieService.getVideos(id) storedIn { videos[id] = it.results }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getReviews(id: Int) {
|
||||||
|
movieService.getReviews(id) storedIn { reviews[id] = it.results }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getKeywords(id: Int) {
|
||||||
|
movieService.getKeywords(id) storedIn { keywords[id] = it.keywords ?: emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getWatchProviders(id: Int) {
|
||||||
|
movieService.getWatchProviders(id) storedIn {
|
||||||
|
it.results[Locale.getDefault().country]?.let { wp ->
|
||||||
|
watchProviders[id] = wp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getExternalIds(id: Int) {
|
||||||
|
movieService.getExternalIds(id) storedIn { externalIds[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAccountStates(id: Int) {
|
||||||
|
val sessionId = SessionManager.currentSession.value?.sessionId ?: throw Exception("Session must not be null")
|
||||||
|
val response = movieService.getAccountStates(id, sessionId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got account states: $it")
|
||||||
|
accountStates[id] = it
|
||||||
|
} ?: run {
|
||||||
|
Log.d(TAG, "Problem getting account states")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Issue getting account states: $response")
|
||||||
|
}
|
||||||
|
// movieService.getAccountStates(id) storedIn { accountStates[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReleaseDates(id: Int) {
|
||||||
|
movieService.getReleaseDates(id) storedIn { releaseDates[id] = it.releaseDates }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun postRating(id: Int, ratingBody: RatingBody) {
|
||||||
|
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteRating(id: Int) {
|
||||||
|
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> {
|
||||||
|
return movieService.getSimilarMovies(id, page)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getPopular(page: Int): Response<out HomePageResponse> {
|
override suspend fun getPopular(page: Int): Response<out HomePageResponse> {
|
||||||
return movieService.getPopularMovies(page)
|
return movieService.getPopularMovies(page)
|
||||||
@@ -36,55 +157,42 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
|
|||||||
override suspend fun getUpcoming(page: Int): Response<out HomePageResponse> {
|
override suspend fun getUpcoming(page: Int): Response<out HomePageResponse> {
|
||||||
return movieService.getUpcomingMovies(page)
|
return movieService.getUpcomingMovies(page)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getReleaseDates(id: Int): Response<MovieReleaseResults> {
|
class SimilarMoviesSource(private val movieId: Int): PagingSource<Int, TmdbItem>(), KoinComponent {
|
||||||
return movieService.getReleaseDates(id)
|
|
||||||
|
private val service: MoviesService by inject()
|
||||||
|
|
||||||
|
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
|
||||||
|
return state.anchorPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getById(id: Int): Response<out DetailedItem> {
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
|
||||||
return movieService.getMovieById(id)
|
return try {
|
||||||
}
|
val nextPage = params.key ?: 1
|
||||||
|
val response = service.getSimilar(movieId, nextPage)
|
||||||
override suspend fun getImages(id: Int): Response<ImageCollection> {
|
if (response.isSuccessful) {
|
||||||
return movieService.getMovieImages(id)
|
val responseBody = response.body()
|
||||||
}
|
val result = responseBody?.results ?: emptyList()
|
||||||
|
LoadResult.Page(
|
||||||
override suspend fun getCastAndCrew(id: Int): Response<CastAndCrew> {
|
data = result,
|
||||||
return movieService.getCastAndCrew(id)
|
prevKey = if (nextPage == 1) {
|
||||||
}
|
null
|
||||||
|
} else {
|
||||||
override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> {
|
nextPage - 1
|
||||||
return movieService.getSimilarMovies(id, page)
|
},
|
||||||
}
|
nextKey = if (result.isEmpty()) {
|
||||||
|
null
|
||||||
override suspend fun getVideos(id: Int): Response<VideoResponse> {
|
} else {
|
||||||
return movieService.getVideos(id)
|
responseBody?.page?.plus(1) ?: (nextPage + 1)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
|
} else {
|
||||||
return movieService.getReviews(id)
|
LoadResult.Invalid()
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
override suspend fun postRating(id: Int, rating: RatingBody): Response<StatusResponse> {
|
return LoadResult.Error(e)
|
||||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
}
|
||||||
return movieService.postMovieRatingAsUser(id, session.sessionId, rating)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteRating(id: Int): Response<StatusResponse> {
|
|
||||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
|
||||||
return movieService.deleteMovieReviewAsUser(id, session.sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
|
|
||||||
return movieService.getKeywords(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getWatchProviders(id: Int): Response<WatchProviderResponse> {
|
|
||||||
return movieService.getWatchProviders(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getExternalIds(id: Int): Response<ExternalIds> {
|
|
||||||
return movieService.getExternalIds(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,93 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCast
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailCrew
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedMovie
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImage
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
|
||||||
|
import okhttp3.internal.notify
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
class PeopleService: KoinComponent {
|
class PeopleService: KoinComponent {
|
||||||
|
|
||||||
|
private val TAG = "PeopleService"
|
||||||
|
|
||||||
private val service by lazy { TmdbClient().createPeopleService() }
|
private val service by lazy { TmdbClient().createPeopleService() }
|
||||||
|
|
||||||
suspend fun getPerson(id: Int): Response<DetailPerson> {
|
val peopleMap = Collections.synchronizedMap(mutableStateMapOf<Int, DetailPerson>())
|
||||||
return service.getPerson(id)
|
val castMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCast>>())
|
||||||
|
val crewMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<DetailCrew>>())
|
||||||
|
val imagesMap = Collections.synchronizedMap(mutableStateMapOf<Int, List<PersonImage>>())
|
||||||
|
val externalIdsMap = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
||||||
|
|
||||||
|
suspend fun getPerson(id: Int) {
|
||||||
|
val response = service.getPerson(id)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got person $id")
|
||||||
|
peopleMap[id] = it
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting person $id")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting person $id")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCredits(id: Int): Response<PersonCreditsResponse> {
|
suspend fun getCredits(id: Int) {
|
||||||
return service.getCredits(id)
|
val response = service.getCredits(id)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got credits $id")
|
||||||
|
castMap[id] = it.cast
|
||||||
|
crewMap[id] = it.crew
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting credits $id")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting credits $id")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getImages(id: Int): Response<PersonImageCollection> {
|
suspend fun getImages(id: Int) {
|
||||||
return service.getImages(id)
|
val response = service.getImages(id)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got images $id")
|
||||||
|
imagesMap[id] = it.images
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting images $id")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issues getting images $id")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPopular(page: Int = 1): Response<HomePagePeopleResponse> {
|
suspend fun getExternalIds(id: Int) {
|
||||||
|
val response = service.getExternalIds(id)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Log.d(TAG, "Successfully got external ids $id")
|
||||||
|
externalIdsMap[id] = it
|
||||||
|
} ?: run {
|
||||||
|
Log.w(TAG, "Problem getting external ids $id")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Issue getting external ids $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getPopular(page: Int): Response<HomePagePeopleResponse> {
|
||||||
return service.getPopular(page)
|
return service.getPopular(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getExternalIds(id: Int): Response<ExternalIds> {
|
|
||||||
return service.getExternalIds(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,46 +1,98 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Collection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Collection
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Keyword
|
||||||
import kotlinx.coroutines.Dispatchers
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ProductionCompany
|
||||||
import kotlinx.coroutines.launch
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
|
||||||
import kotlinx.coroutines.withContext
|
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 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
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SearchService: KoinComponent {
|
class SearchService: KoinComponent {
|
||||||
|
|
||||||
private val service: SearchApi by inject()
|
private val service: SearchService by inject()
|
||||||
|
|
||||||
suspend fun searchCompanies(query: String, page: Int = 1): Response<SearchResult<ProductionCompany>> {
|
val movieResults = mutableStateOf<Flow<PagingData<SearchResultMovie>>?>(null)
|
||||||
|
val tvResults = mutableStateOf<Flow<PagingData<SearchResultTv>>?>(null)
|
||||||
|
val peopleResults = mutableStateOf<Flow<PagingData<SearchResultPerson>>?>(null)
|
||||||
|
val multiResults = mutableStateOf<Flow<PagingData<SortableSearchResult>>?>(null)
|
||||||
|
|
||||||
|
fun searchCompanies(query: String, page: Int = 1): Response<SearchResult<ProductionCompany>> {
|
||||||
return service.searchCompanies(query, page)
|
return service.searchCompanies(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchCollections(query: String, page: Int = 1): Response<SearchResult<Collection>> {
|
fun searchCollections(query: String, page: Int = 1): Response<SearchResult<Collection>> {
|
||||||
return service.searchCollections(query, page)
|
return service.searchCollections(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchKeywords(query: String, page: Int = 1): Response<SearchResult<Keyword>> {
|
fun searchKeywords(query: String, page: Int = 1): Response<SearchResult<Keyword>> {
|
||||||
return service.searchKeywords(query, page)
|
return service.searchKeywords(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchMovies(query: String, page: Int = 1): Response<SearchResult<SearchResultMovie>> {
|
fun searchMovies(query: String, page: Int): Response<SearchResult<SearchResultMovie>> {
|
||||||
return service.searchMovies(query, page)
|
return service.searchMovies(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchTv(query: String, page: Int = 1): Response<SearchResult<SearchResultTv>> {
|
fun searchTv(query: String, page: Int): Response<SearchResult<SearchResultTv>> {
|
||||||
return service.searchTv(query, page)
|
return service.searchTv(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchPeople(query: String, page: Int = 1): Response<SearchResult<SearchResultPerson>> {
|
fun searchPeople(query: String, page: Int): Response<SearchResult<SearchResultPerson>> {
|
||||||
return service.searchPeople(query, page)
|
return service.searchPeople(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun searchMulti(query: String, page: Int = 1): Response<SearchResult<SortableSearchResult>> {
|
fun searchMulti(query: String, page: Int): Response<SearchResult<SortableSearchResult>> {
|
||||||
return service.searchMulti(query, page)
|
return service.searchMulti(query, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -64,4 +64,7 @@ interface TvApi {
|
|||||||
@GET("tv/{id}/external_ids")
|
@GET("tv/{id}/external_ids")
|
||||||
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
|
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
|
||||||
|
|
||||||
|
@GET("tv/{id}/account_states")
|
||||||
|
suspend fun getAccountStates(@Path("id") id: Int): Response<AccountStates>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,148 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
package com.owenlejeune.tvtime.api.tmdb.api.v3
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import com.owenlejeune.tvtime.api.storedIn
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastAndCrew
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastAndCrew
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CastMember
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CrewMember
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedTv
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Keyword
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Review
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class TvService: KoinComponent, DetailService, HomePageService {
|
class TvService: KoinComponent, DetailService, HomePageService {
|
||||||
|
|
||||||
private val service by lazy { TmdbClient().createTvService() }
|
companion object {
|
||||||
|
private const val TAG = "TvService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val service: TvApi by inject()
|
||||||
|
|
||||||
|
val detailTv = Collections.synchronizedMap(mutableStateMapOf<Int, DetailedTv>())
|
||||||
|
val images = Collections.synchronizedMap(mutableStateMapOf<Int, ImageCollection>())
|
||||||
|
val cast = Collections.synchronizedMap(mutableStateMapOf<Int, List<CastMember>>())
|
||||||
|
val crew = Collections.synchronizedMap(mutableStateMapOf<Int, List<CrewMember>>())
|
||||||
|
val videos = Collections.synchronizedMap(mutableStateMapOf<Int, List<Video>>())
|
||||||
|
val reviews = Collections.synchronizedMap(mutableStateMapOf<Int, List<Review>>())
|
||||||
|
val keywords = Collections.synchronizedMap(mutableStateMapOf<Int, List<Keyword>>())
|
||||||
|
val watchProviders = Collections.synchronizedMap(mutableStateMapOf<Int, WatchProviders>())
|
||||||
|
val externalIds = Collections.synchronizedMap(mutableStateMapOf<Int, ExternalIds>())
|
||||||
|
val contentRatings = Collections.synchronizedMap(mutableStateMapOf<Int, List<TvContentRatings.TvContentRating>>())
|
||||||
|
val similar = Collections.synchronizedMap(mutableStateMapOf<Int, Flow<PagingData<TmdbItem>>>())
|
||||||
|
val accountStates = Collections.synchronizedMap(mutableStateMapOf<Int, AccountStates>())
|
||||||
|
|
||||||
|
private val _seasons = Collections.synchronizedMap(mutableStateMapOf<Int, MutableSet<Season>>())
|
||||||
|
val seasons: MutableMap<Int, out Set<Season>>
|
||||||
|
get() = _seasons
|
||||||
|
|
||||||
|
override suspend fun getById(id: Int) {
|
||||||
|
service.getTvShowById(id) storedIn { detailTv[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getImages(id: Int) {
|
||||||
|
service.getTvImages(id) storedIn { images[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCastAndCrew(id: Int) {
|
||||||
|
service.getCastAndCrew(id) storedIn {
|
||||||
|
cast[id] = it.cast
|
||||||
|
crew[id] = it.crew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getContentRatings(id: Int) {
|
||||||
|
service.getContentRatings(id) storedIn { contentRatings[id] = it.results }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getVideos(id: Int) {
|
||||||
|
service.getVideos(id) storedIn { videos[id] = it.results }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getReviews(id: Int) {
|
||||||
|
service.getReviews(id) storedIn { reviews[id] = it.results }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getKeywords(id: Int) {
|
||||||
|
service.getKeywords(id) storedIn { keywords[id] = it.keywords ?: emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getWatchProviders(id: Int) {
|
||||||
|
service.getWatchProviders(id) storedIn {
|
||||||
|
it.results[Locale.getDefault().country]?.let { wp ->
|
||||||
|
watchProviders[id] = wp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAccountStates(id: Int) {
|
||||||
|
service.getAccountStates(id) storedIn { accountStates[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeason(seriesId: Int, seasonId: Int) {
|
||||||
|
service.getSeason(seriesId, seasonId) storedIn {
|
||||||
|
_seasons[seriesId]?.add(it) ?: run {
|
||||||
|
_seasons[seriesId] = mutableSetOf(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getExternalIds(id: Int) {
|
||||||
|
service.getExternalIds(id) storedIn { externalIds[id] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun postRating(id: Int, ratingBody: RatingBody) {
|
||||||
|
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteRating(id: Int) {
|
||||||
|
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getPopular(page: Int): Response<out HomePageResponse> {
|
override suspend fun getPopular(page: Int): Response<out HomePageResponse> {
|
||||||
return service.getPoplarTv(page)
|
return service.getPoplarTv(page)
|
||||||
@@ -38,58 +160,42 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
return service.getTvOnTheAir(page)
|
return service.getTvOnTheAir(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getById(id: Int): Response<out DetailedItem> {
|
}
|
||||||
return service.getTvShowById(id)
|
|
||||||
|
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 getImages(id: Int): Response<ImageCollection> {
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
|
||||||
return service.getTvImages(id)
|
return try {
|
||||||
}
|
val nextPage = params.key ?: 1
|
||||||
|
val response = service.getSimilar(tvId, nextPage)
|
||||||
override suspend fun getCastAndCrew(id: Int): Response<CastAndCrew> {
|
if (response.isSuccessful) {
|
||||||
return service.getCastAndCrew(id)
|
val responseBody = response.body()
|
||||||
}
|
val result = responseBody?.results ?: emptyList()
|
||||||
|
LoadResult.Page(
|
||||||
suspend fun getContentRatings(id: Int): Response<TvContentRatings> {
|
data = result,
|
||||||
return service.getContentRatings(id)
|
prevKey = if (nextPage == 1) {
|
||||||
}
|
null
|
||||||
|
} else {
|
||||||
override suspend fun getSimilar(id: Int, page: Int): Response<out HomePageResponse> {
|
nextPage - 1
|
||||||
return service.getSimilarTvShows(id, page)
|
},
|
||||||
}
|
nextKey = if (result.isEmpty()) {
|
||||||
|
null
|
||||||
override suspend fun getVideos(id: Int): Response<VideoResponse> {
|
} else {
|
||||||
return service.getVideos(id)
|
responseBody?.page?.plus(1) ?: (nextPage + 1)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
override suspend fun getReviews(id: Int): Response<ReviewResponse> {
|
} else {
|
||||||
return service.getReviews(id)
|
LoadResult.Invalid()
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
override suspend fun postRating(id: Int, ratingBody: RatingBody): Response<StatusResponse> {
|
return LoadResult.Error(e)
|
||||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
}
|
||||||
return service.postTvRatingAsUser(id, session.sessionId, ratingBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteRating(id: Int): Response<StatusResponse> {
|
|
||||||
val session = SessionManager.currentSession.value ?: throw Exception("Session must not be null")
|
|
||||||
return service.deleteTvReviewAsUser(id, session.sessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getKeywords(id: Int): Response<KeywordsResponse> {
|
|
||||||
return service.getKeywords(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getWatchProviders(id: Int): Response<WatchProviderResponse> {
|
|
||||||
return service.getWatchProviders(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getSeason(seriesId: Int, seasonId: Int): Response<Season> {
|
|
||||||
return service.getSeason(seriesId, seasonId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getExternalIds(id: Int): Response<ExternalIds> {
|
|
||||||
return service.getExternalIds(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.BaseDeserializer
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||||
|
|
||||||
|
class AccountStatesDeserializer: BaseDeserializer<AccountStates>() {
|
||||||
|
|
||||||
|
override fun processJson(obj: JsonObject): AccountStates {
|
||||||
|
val id = obj.get("id").asInt
|
||||||
|
val isFavorite = obj.get("favorite").asBoolean
|
||||||
|
val isWatchlist = obj.get("watchlist").asBoolean
|
||||||
|
return try {
|
||||||
|
val isRated = obj.get("rated").asBoolean
|
||||||
|
AccountStates(id, isFavorite, isWatchlist, isRated, -1)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val rating = obj.get("rated").asJsonObject.get("value").asInt
|
||||||
|
AccountStates(id, isFavorite, isWatchlist, true, rating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class AccountStates(
|
||||||
|
@SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerializedName("favorite")
|
||||||
|
val isFavorite: Boolean,
|
||||||
|
@SerializedName("watchlist")
|
||||||
|
val isWatchListed: Boolean,
|
||||||
|
val isRated: Boolean,
|
||||||
|
val rating: Int
|
||||||
|
)
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
class GuestSessionResponse(
|
|
||||||
@SerializedName("success") val success: Boolean,
|
|
||||||
@SerializedName("guest_session_id") val guestSessionId: String,
|
|
||||||
@SerializedName("expires_at") val expiry: String
|
|
||||||
)
|
|
||||||
@@ -12,8 +12,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class HomePagePeoplePagingSource: PagingSource<Int, HomePagePerson>(), KoinComponent {
|
class HomePagePeoplePagingSource: PagingSource<Int, HomePagePerson>(), KoinComponent {
|
||||||
|
|
||||||
private val service: PeopleApi by inject()
|
private val service: PeopleService by inject()
|
||||||
private val context: Context by inject()
|
|
||||||
|
|
||||||
override fun getRefreshKey(state: PagingState<Int, HomePagePerson>): Int? {
|
override fun getRefreshKey(state: PagingState<Int, HomePagePerson>): Int? {
|
||||||
return state.anchorPosition
|
return state.anchorPosition
|
||||||
@@ -32,7 +31,6 @@ class HomePagePeoplePagingSource: PagingSource<Int, HomePagePerson>(), KoinCompo
|
|||||||
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1
|
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Toast.makeText(context, "No more results found", Toast.LENGTH_SHORT).show()
|
|
||||||
LoadResult.Invalid()
|
LoadResult.Invalid()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import retrofit2.http.*
|
|||||||
interface ListV4Api {
|
interface ListV4Api {
|
||||||
|
|
||||||
@GET("list/{id}")
|
@GET("list/{id}")
|
||||||
suspend fun getList(
|
suspend fun getList(@Path("id") listId: Int): Response<MediaList>
|
||||||
@Path("id") listId: Int,
|
|
||||||
@Query("api_key") apiKey: String,
|
|
||||||
@Query("page") page: Int = 1
|
|
||||||
): Response<MediaList>
|
|
||||||
|
|
||||||
@POST("list")
|
@POST("list")
|
||||||
suspend fun createList(@Body body: CreateListBody): Response<CreateListResponse>
|
suspend fun createList(@Body body: CreateListBody): Response<CreateListResponse>
|
||||||
|
|||||||
@@ -1,52 +1,74 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v4
|
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.BuildConfig
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
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
|
||||||
|
|
||||||
class ListV4Service: KoinComponent {
|
class ListV4Service: KoinComponent {
|
||||||
|
|
||||||
private val service by lazy { TmdbClient().createV4ListService() }
|
companion object {
|
||||||
|
private const val TAG = "ListV4Service"
|
||||||
private val preferences: AppPreferences by inject()
|
|
||||||
|
|
||||||
suspend fun getList(listId: Int, page: Int = 1): Response<MediaList> {
|
|
||||||
return service.getList(listId, BuildConfig.TMDB_Api_v4Key, page)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createList(body: CreateListBody): Response<CreateListResponse> {
|
private val service: ListV4Api by inject()
|
||||||
return service.createList(body)
|
|
||||||
|
val listMap = mutableStateMapOf<Int, MediaList>()
|
||||||
|
|
||||||
|
suspend fun getList(listId: Int) {
|
||||||
|
val response = service.getList(listId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
listMap[listId] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateList(listId: Int, body: ListUpdateBody): Response<StatusResponse> {
|
suspend fun createList(body: CreateListBody) {//}: Response<CreateListResponse> {
|
||||||
return service.updateList(listId, body)
|
service.createList(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun clearList(listId: Int): Response<ClearListResponse> {
|
suspend fun updateList(listId: Int, body: ListUpdateBody) {
|
||||||
return service.clearList(listId)
|
val response = service.updateList(listId, body)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
Log.d(TAG, "Successfully updated list $listId")
|
||||||
|
getList(listId)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Issue updating list $listId")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteList(listId: Int): Response<StatusResponse> {
|
suspend fun deleteListItems(listId: Int, body: DeleteListItemsBody) {
|
||||||
return service.deleteList(listId)
|
val response = service.deleteListItems(listId, body)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.List)
|
||||||
|
getList(listId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addItemsToList(listId: Int, body: AddToListBody): Response<AddToListResponse> {
|
suspend fun clearList(listId: Int) {//}: Response<ClearListResponse> {
|
||||||
return service.addItemsToList(listId, body)
|
service.clearList(listId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateListItems(listId: Int, body: UpdateListItemBody): Response<AddToListResponse> {
|
suspend fun deleteList(listId: Int) {//}: Response<StatusResponse> {
|
||||||
return service.updateListItems(listId, body)
|
service.deleteList(listId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteListItems(listId: Int, body: DeleteListItemsBody): Response<AddToListResponse> {
|
suspend fun addItemsToList(listId: Int, body: AddToListBody) {//}: Response<AddToListResponse> {
|
||||||
return service.deleteListItems(listId, body)
|
service.addItemsToList(listId, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getListItemStatus(listId: Int, mediaId: Int, mediaType: String): Response<ListItemStatusResponse> {
|
suspend fun updateListItems(listId: Int, body: UpdateListItemBody) {//}: Response<AddToListResponse> {
|
||||||
return service.getListItemStatus(listId, mediaId, mediaType)
|
service.updateListItems(listId, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getListItemStatus(listId: Int, mediaId: Int, mediaType: String) {//}: Response<ListItemStatusResponse> {
|
||||||
|
service.getListItemStatus(listId, mediaId, mediaType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
//package com.owenlejeune.tvtime.api.tmdb.paging
|
|
||||||
//
|
|
||||||
//import androidx.paging.PagingSource
|
|
||||||
//import androidx.paging.PagingState
|
|
||||||
//import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
|
||||||
//import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PopularMovie
|
|
||||||
//import retrofit2.HttpException
|
|
||||||
//import java.io.IOException
|
|
||||||
//
|
|
||||||
//class PopularMovieSource: PagingSource<Int, PopularMovie>() {
|
|
||||||
//
|
|
||||||
// companion object {
|
|
||||||
// const val MIN_PAGE = 1
|
|
||||||
// const val MAX_PAGE = 1000
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private val movieService by lazy { TmdbClient().createMovieService() }
|
|
||||||
//
|
|
||||||
// override fun getRefreshKey(state: PagingState<Int, PopularMovie>): Int? {
|
|
||||||
// return state.anchorPosition
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PopularMovie> {
|
|
||||||
// return try {
|
|
||||||
// val nextPage = params.key ?: 1
|
|
||||||
// val movieList = movieService.getPopularMovies(page = nextPage)
|
|
||||||
// LoadResult.Page(
|
|
||||||
// data = movieList.movies,
|
|
||||||
// prevKey = if (nextPage == MIN_PAGE) null else nextPage - 1,
|
|
||||||
// nextKey = if (movieList.count == 0 || nextPage > MAX_PAGE) null else movieList.page + 1
|
|
||||||
// )
|
|
||||||
// } catch (exception: IOException) {
|
|
||||||
// return LoadResult.Error(exception)
|
|
||||||
// } catch (exception: HttpException) {
|
|
||||||
// return LoadResult.Error(exception)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -6,11 +6,21 @@ import com.owenlejeune.tvtime.BuildConfig
|
|||||||
import com.owenlejeune.tvtime.api.*
|
import com.owenlejeune.tvtime.api.*
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
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.ConfigurationService
|
||||||
|
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.SearchService
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.AccountStatesDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.AccountStates
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
|
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.ListV4Service
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
@@ -30,7 +40,6 @@ val networkModule = module {
|
|||||||
single { get<TmdbClient>().createV4AccountService() }
|
single { get<TmdbClient>().createV4AccountService() }
|
||||||
single { get<TmdbClient>().createV4ListService() }
|
single { get<TmdbClient>().createV4ListService() }
|
||||||
single { get<TmdbClient>().createAccountService() }
|
single { get<TmdbClient>().createAccountService() }
|
||||||
single { get<TmdbClient>().createGuestSessionService() }
|
|
||||||
single { get<TmdbClient>().createAuthenticationService() }
|
single { get<TmdbClient>().createAuthenticationService() }
|
||||||
single { get<TmdbClient>().createMovieService() }
|
single { get<TmdbClient>().createMovieService() }
|
||||||
single { get<TmdbClient>().createPeopleService() }
|
single { get<TmdbClient>().createPeopleService() }
|
||||||
@@ -38,14 +47,23 @@ val networkModule = module {
|
|||||||
single { get<TmdbClient>().createTvService() }
|
single { get<TmdbClient>().createTvService() }
|
||||||
single { get<TmdbClient>().createConfigurationService() }
|
single { get<TmdbClient>().createConfigurationService() }
|
||||||
|
|
||||||
|
single { ConfigurationService() }
|
||||||
|
single { MoviesService() }
|
||||||
|
single { TvService() }
|
||||||
single { AccountService() }
|
single { AccountService() }
|
||||||
|
single { AuthenticationService() }
|
||||||
|
single { PeopleService() }
|
||||||
|
single { SearchService() }
|
||||||
single { AccountV4Service() }
|
single { AccountV4Service() }
|
||||||
|
single { AuthenticationV4Service() }
|
||||||
|
single { ListV4Service() }
|
||||||
|
|
||||||
single<Map<Class<*>, JsonDeserializer<*>>> {
|
single<Map<Class<*>, JsonDeserializer<*>>> {
|
||||||
mapOf(
|
mapOf(
|
||||||
ListItem::class.java to ListItemDeserializer(),
|
ListItem::class.java to ListItemDeserializer(),
|
||||||
KnownFor::class.java to KnownForDeserializer(),
|
KnownFor::class.java to KnownForDeserializer(),
|
||||||
SortableSearchResult::class.java to SortableSearchResultDeserializer()
|
SortableSearchResult::class.java to SortableSearchResultDeserializer(),
|
||||||
|
AccountStates::class.java to AccountStatesDeserializer()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.owenlejeune.tvtime.extensions
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
fun Any.coroutineTask(runnable: suspend () -> Unit) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch { runnable() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.Animatable
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredWidthIn
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Bookmark
|
||||||
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.List
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
||||||
|
import com.owenlejeune.tvtime.ui.theme.RatingSelected
|
||||||
|
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
||||||
|
import com.owenlejeune.tvtime.ui.theme.actionButtonColor
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||||
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
enum class Actions {
|
||||||
|
RATE,
|
||||||
|
WATCHLIST,
|
||||||
|
LIST,
|
||||||
|
FAVORITE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionsView(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
actions: List<Actions> = listOf(Actions.RATE, Actions.WATCHLIST, Actions.LIST, Actions.FAVORITE),
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val accountViewModel = viewModel<AccountViewModel>()
|
||||||
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
mainViewModel.getAccountStates(itemId, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
if (actions.contains(Actions.RATE)) {
|
||||||
|
RateButton(
|
||||||
|
itemId = itemId,
|
||||||
|
type = type,
|
||||||
|
mainViewModel = mainViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (actions.contains(Actions.WATCHLIST)) {
|
||||||
|
WatchlistButton(
|
||||||
|
itemId = itemId,
|
||||||
|
type = type,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
mainViewModel = mainViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (actions.contains(Actions.FAVORITE)) {
|
||||||
|
FavoriteButton(
|
||||||
|
itemId = itemId,
|
||||||
|
type = type,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
mainViewModel = mainViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (actions.contains(Actions.LIST)) {
|
||||||
|
ListButton(
|
||||||
|
itemId = itemId,
|
||||||
|
type = type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionButton(
|
||||||
|
imageVector: ImageVector,
|
||||||
|
contentDescription: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
filledIconColor: Color,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val bgColor = MaterialTheme.colorScheme.background
|
||||||
|
val tintColor = remember { Animatable(bgColor) }
|
||||||
|
LaunchedEffect(isSelected) {
|
||||||
|
val target = if (isSelected) filledIconColor else bgColor
|
||||||
|
tintColor.animateTo(targetValue = target, animationSpec = tween(300))
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.height(40.dp)
|
||||||
|
.requiredWidthIn(min = 40.dp)
|
||||||
|
.background(color = MaterialTheme.colorScheme.actionButtonColor)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
imageVector = imageVector,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = tintColor.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RateButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
mainViewModel: MainViewModel,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val accountStates = remember { mainViewModel.produceAccountStatesFor(type) }
|
||||||
|
val itemIsRated = accountStates[itemId]?.isRated ?: false
|
||||||
|
|
||||||
|
val showRatingDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ActionButton(
|
||||||
|
imageVector = Icons.Filled.Star,
|
||||||
|
contentDescription = "",
|
||||||
|
isSelected = itemIsRated,
|
||||||
|
filledIconColor = RatingSelected,
|
||||||
|
onClick = { showRatingDialog.value = true },
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
|
||||||
|
val userRating = accountStates[itemId]?.rating?.times(2)?.toFloat() ?: 0f
|
||||||
|
RatingDialog(
|
||||||
|
showDialog = showRatingDialog,
|
||||||
|
rating = userRating,
|
||||||
|
onValueConfirmed = { rating ->
|
||||||
|
if (rating > 0f) {
|
||||||
|
scope.launch { mainViewModel.postRating(itemId, rating, type) }
|
||||||
|
} else {
|
||||||
|
scope.launch { mainViewModel.deleteRating(itemId, type) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchlistButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
mainViewModel: MainViewModel,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val accountStates = remember { mainViewModel.produceAccountStatesFor(type) }
|
||||||
|
val itemIsWatchlisted = accountStates[itemId]?.isWatchListed ?: false
|
||||||
|
|
||||||
|
ActionButton(
|
||||||
|
modifier = modifier,
|
||||||
|
imageVector = Icons.Filled.Bookmark,
|
||||||
|
contentDescription = "",
|
||||||
|
isSelected = itemIsWatchlisted,
|
||||||
|
filledIconColor = WatchlistSelected,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
accountViewModel.addToWatchlist(type, itemId, !itemIsWatchlisted)
|
||||||
|
mainViewModel.getAccountStates(itemId, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ListButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
CircleBackgroundColorImage(
|
||||||
|
modifier = modifier.clickable(
|
||||||
|
onClick = {}
|
||||||
|
),
|
||||||
|
size = 40.dp,
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.actionButtonColor,
|
||||||
|
image = Icons.Filled.List,
|
||||||
|
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.background),
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FavoriteButton(
|
||||||
|
itemId: Int,
|
||||||
|
type: MediaViewType,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
mainViewModel: MainViewModel,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val accountStates = remember { mainViewModel.produceAccountStatesFor(type) }
|
||||||
|
val itemIsFavorited = accountStates[itemId]?.isFavorite ?: false
|
||||||
|
|
||||||
|
ActionButton(
|
||||||
|
modifier = modifier,
|
||||||
|
imageVector = Icons.Filled.Favorite,
|
||||||
|
contentDescription = "",
|
||||||
|
isSelected = itemIsFavorited,
|
||||||
|
filledIconColor = FavoriteSelected,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
accountViewModel.addToFavourites(type, itemId, !itemIsFavorited)
|
||||||
|
mainViewModel.getAccountStates(itemId, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RatingDialog(
|
||||||
|
showDialog: MutableState<Boolean>,
|
||||||
|
rating: Float,
|
||||||
|
onValueConfirmed: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
val formatPosition: (Float) -> String = { position ->
|
||||||
|
DecimalFormat("#.#").format(position.toInt()*5/10f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialog.value) {
|
||||||
|
var sliderPosition by remember { mutableStateOf(rating) }
|
||||||
|
val formatted = formatPosition(sliderPosition).toFloat()
|
||||||
|
AlertDialog(
|
||||||
|
modifier = Modifier.wrapContentHeight(),
|
||||||
|
onDismissRequest = { showDialog.value = false },
|
||||||
|
title = { Text(text = stringResource(R.string.rating_dialog_title)) },
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.height(40.dp),
|
||||||
|
onClick = {
|
||||||
|
onValueConfirmed.invoke(formatted)
|
||||||
|
showDialog.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (formatted > 0f) {
|
||||||
|
stringResource(id = R.string.rating_dialog_confirm)
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.rating_dialog_delete)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.height(40.dp),
|
||||||
|
onClick = {
|
||||||
|
showDialog.value = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
SliderWithLabel(
|
||||||
|
value = sliderPosition,
|
||||||
|
valueRange = 0f..20f,
|
||||||
|
onValueChanged = {
|
||||||
|
sliderPosition = it
|
||||||
|
},
|
||||||
|
sliderLabel = "${sliderPosition.toInt() * 5}%"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,10 +19,11 @@ fun SliderWithLabel(
|
|||||||
valueRange: ClosedFloatingPointRange<Float>,
|
valueRange: ClosedFloatingPointRange<Float>,
|
||||||
onValueChanged: (Float) -> Unit,
|
onValueChanged: (Float) -> Unit,
|
||||||
sliderLabel: String,
|
sliderLabel: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
steps: Int = 0,
|
steps: Int = 0,
|
||||||
labelMinWidth: Dp = 36.dp
|
labelMinWidth: Dp = 46.dp
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(modifier = modifier) {
|
||||||
BoxWithConstraints(
|
BoxWithConstraints(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -60,7 +61,7 @@ fun SliderWithLabel(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SliderLabel(label: String, minWidth: Dp, modifier: Modifier = Modifier) {
|
fun SliderLabel(label: String, minWidth: Dp, modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
label,
|
text = label,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ sealed class AccountTabNavItem(
|
|||||||
val noContentText = resourceUtils.getString(noContentStringRes)
|
val noContentText = resourceUtils.getString(noContentStringRes)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val GuestItems
|
|
||||||
get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes)
|
|
||||||
|
|
||||||
val AuthorizedItems
|
val AuthorizedItems
|
||||||
get() = listOf(
|
get() = listOf(
|
||||||
RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows,
|
RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens
|
package com.owenlejeune.tvtime.ui.screens
|
||||||
|
import android.accounts.Account
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -35,6 +36,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.constraintlayout.compose.Dimension
|
import androidx.constraintlayout.compose.Dimension
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
@@ -47,11 +49,15 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
|||||||
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||||
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
||||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
|
import com.owenlejeune.tvtime.ui.components.Actions
|
||||||
|
import com.owenlejeune.tvtime.ui.components.ActionsView
|
||||||
|
import com.owenlejeune.tvtime.ui.components.FavoriteButton
|
||||||
import com.owenlejeune.tvtime.ui.components.RatingView
|
import com.owenlejeune.tvtime.ui.components.RatingView
|
||||||
import com.owenlejeune.tvtime.ui.components.Spinner
|
import com.owenlejeune.tvtime.ui.components.Spinner
|
||||||
import com.owenlejeune.tvtime.ui.components.SwitchPreference
|
import com.owenlejeune.tvtime.ui.components.SwitchPreference
|
||||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||||
import com.owenlejeune.tvtime.ui.theme.*
|
import com.owenlejeune.tvtime.ui.theme.*
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
|
||||||
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
|
||||||
@@ -68,22 +74,22 @@ import kotlin.math.roundToInt
|
|||||||
@Composable
|
@Composable
|
||||||
fun ListDetailScreen(
|
fun ListDetailScreen(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
itemId: Int?,
|
itemId: Int,
|
||||||
windowSize: WindowSizeClass,
|
windowSize: WindowSizeClass,
|
||||||
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
service: ListV4Service = KoinJavaComponent.get(ListV4Service::class.java)
|
||||||
) {
|
) {
|
||||||
|
val accountViewModel = viewModel<AccountViewModel>()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
accountViewModel.getList(itemId)
|
||||||
|
}
|
||||||
|
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
|
|
||||||
val service = ListV4Service()
|
val listMap = remember { accountViewModel.listMap }
|
||||||
|
val parentList = listMap[itemId]
|
||||||
|
|
||||||
val parentList = remember { mutableStateOf<MediaList?>(null) }
|
|
||||||
itemId?.let {
|
|
||||||
if (parentList.value == null) {
|
|
||||||
fetchList(itemId, service, parentList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
@@ -101,7 +107,7 @@ fun ListDetailScreen(
|
|||||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||||
titleContentColor = MaterialTheme.colorScheme.primary
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
),
|
),
|
||||||
title = { Text(text = parentList.value?.name ?: "") },
|
title = { Text(text = parentList?.name ?: "") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { appNavController.popBackStack() }
|
onClick = { appNavController.popBackStack() }
|
||||||
@@ -117,7 +123,7 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(modifier = Modifier.padding(innerPadding)) {
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
parentList.value?.let { mediaList ->
|
parentList?.let { mediaList ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 12.dp)
|
.padding(all = 12.dp)
|
||||||
@@ -151,7 +157,7 @@ private fun ListHeader(
|
|||||||
list: MediaList,
|
list: MediaList,
|
||||||
selectedSortOrder: MutableState<SortOrder>,
|
selectedSortOrder: MutableState<SortOrder>,
|
||||||
service: ListV4Service,
|
service: ListV4Service,
|
||||||
parentList: MutableState<MediaList?>
|
parentList: MediaList?
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -254,10 +260,7 @@ private fun ListHeader(
|
|||||||
if (showEditListDialog.value) {
|
if (showEditListDialog.value) {
|
||||||
EditListDialog(
|
EditListDialog(
|
||||||
showEditListDialog = showEditListDialog,
|
showEditListDialog = showEditListDialog,
|
||||||
list = list,
|
list = list
|
||||||
service = service,
|
|
||||||
parentList = parentList,
|
|
||||||
selectedSortOrder = selectedSortOrder
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,11 +315,10 @@ private fun SortOrderDialog(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun EditListDialog(
|
private fun EditListDialog(
|
||||||
showEditListDialog: MutableState<Boolean>,
|
showEditListDialog: MutableState<Boolean>,
|
||||||
list: MediaList,
|
list: MediaList
|
||||||
service: ListV4Service,
|
|
||||||
parentList: MutableState<MediaList?>,
|
|
||||||
selectedSortOrder: MutableState<SortOrder>
|
|
||||||
) {
|
) {
|
||||||
|
val accountViewModel = viewModel<AccountViewModel>()
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
var listTitle by remember { mutableStateOf(list.name) }
|
var listTitle by remember { mutableStateOf(list.name) }
|
||||||
@@ -338,11 +340,7 @@ private fun EditListDialog(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val listUpdateBody = ListUpdateBody(listTitle, listDescription, isPublicList, editSelectedSortOrder)
|
val listUpdateBody = ListUpdateBody(listTitle, listDescription, isPublicList, editSelectedSortOrder)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val response = service.updateList(list.id, listUpdateBody)
|
accountViewModel.updateList(list.id, listUpdateBody)
|
||||||
if (response.isSuccessful) {
|
|
||||||
fetchList(list.id, service, parentList)
|
|
||||||
selectedSortOrder.value = editSelectedSortOrder
|
|
||||||
}
|
|
||||||
showEditListDialog.value = false
|
showEditListDialog.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,23 +424,24 @@ private fun RowScope.OverviewStatCard(
|
|||||||
private fun ListItemView(
|
private fun ListItemView(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
listItem: ListItem,
|
listItem: ListItem,
|
||||||
list: MutableState<MediaList?>
|
list: MediaList?
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val accountViewModel = viewModel<AccountViewModel>()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
RevealSwipe (
|
RevealSwipe (
|
||||||
directions = setOf(RevealDirection.EndToStart),
|
directions = setOf(RevealDirection.EndToStart),
|
||||||
hiddenContentEnd = {
|
hiddenContentEnd = {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.padding(horizontal = 15.dp),
|
modifier = Modifier.padding(horizontal = 15.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
removeItemFromList(
|
scope.launch {
|
||||||
context = context,
|
accountViewModel.deleteListItem(
|
||||||
itemId = listItem.id,
|
list?.id ?: -1,
|
||||||
itemType = listItem.mediaType,
|
listItem.id,
|
||||||
itemName = listItem.title,
|
listItem.mediaType
|
||||||
service = ListV4Service(),
|
)
|
||||||
list = list
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -533,7 +532,11 @@ private fun ListItemView(
|
|||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
ActionButtonRow(listItem)
|
ActionsView(
|
||||||
|
itemId = listItem.id,
|
||||||
|
type = listItem.mediaType,
|
||||||
|
actions = listOf(Actions.RATE, Actions.WATCHLIST, Actions.FAVORITE)
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,109 +556,6 @@ private fun ListItemView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ActionButtonRow(listItem: ListItem) {
|
|
||||||
val session = SessionManager.currentSession.value
|
|
||||||
|
|
||||||
val (isFavourited, isWatchlisted, isRated) = if (listItem.mediaType == MediaViewType.MOVIE) {
|
|
||||||
Triple(
|
|
||||||
session?.hasFavoritedMovie(listItem.id) == true,
|
|
||||||
session?.hasWatchlistedMovie(listItem.id) == true,
|
|
||||||
session?.hasRatedMovie(listItem.id) == true
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Triple(
|
|
||||||
session?.hasFavoritedTvShow(listItem.id) == true,
|
|
||||||
session?.hasWatchlistedTvShow(listItem.id) == true,
|
|
||||||
session?.hasRatedTvShow(listItem.id) == true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
ActionButton(
|
|
||||||
itemId = listItem.id,
|
|
||||||
type = listItem.mediaType,
|
|
||||||
imageVector = Icons.Filled.Favorite,
|
|
||||||
contentDescription = stringResource(id = R.string.favourite_label),
|
|
||||||
isSelected = isFavourited,
|
|
||||||
filledIconColor = FavoriteSelected,
|
|
||||||
onClick = ::listAddToFavorite
|
|
||||||
)
|
|
||||||
|
|
||||||
ActionButton(
|
|
||||||
itemId = listItem.id,
|
|
||||||
type = listItem.mediaType,
|
|
||||||
imageVector = Icons.Filled.Bookmark,
|
|
||||||
contentDescription = "",
|
|
||||||
isSelected = isWatchlisted,
|
|
||||||
filledIconColor = WatchlistSelected,
|
|
||||||
onClick = ::listAddToWatchlist
|
|
||||||
)
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
ActionButton(
|
|
||||||
itemId = listItem.id,
|
|
||||||
type = listItem.mediaType,
|
|
||||||
imageVector = Icons.Filled.Star,
|
|
||||||
contentDescription = "",
|
|
||||||
isSelected = isRated,
|
|
||||||
filledIconColor = RatingSelected,
|
|
||||||
onClick = { c, i, t, s, f ->
|
|
||||||
// todo - add rating
|
|
||||||
Toast.makeText(context, "Rating", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun listAddToWatchlist(
|
|
||||||
context: Context,
|
|
||||||
itemId: Int,
|
|
||||||
type: MediaViewType,
|
|
||||||
itemIsWatchlisted: MutableState<Boolean>,
|
|
||||||
onWatchlistChanged: (Boolean) -> Unit
|
|
||||||
) {
|
|
||||||
val currentSession = SessionManager.currentSession.value
|
|
||||||
val accountId = currentSession!!.accountDetails.value!!.id
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = AccountService().addToWatchlist(accountId, WatchlistBody(type, itemId, !itemIsWatchlisted.value))
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
currentSession.refresh(changed = SessionManager.Session.Changed.Watchlist)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
itemIsWatchlisted.value = !itemIsWatchlisted.value
|
|
||||||
onWatchlistChanged(itemIsWatchlisted.value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "An error occurred", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun listAddToFavorite(
|
|
||||||
context: Context,
|
|
||||||
itemId: Int,
|
|
||||||
type: MediaViewType,
|
|
||||||
itemIsFavorited: MutableState<Boolean>,
|
|
||||||
onFavoriteChanged: (Boolean) -> Unit
|
|
||||||
) {
|
|
||||||
val currentSession = SessionManager.currentSession.value
|
|
||||||
val accountId = currentSession!!.accountDetails.value!!.id
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = AccountService().markAsFavorite(accountId, MarkAsFavoriteBody(type, itemId, !itemIsFavorited.value))
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
currentSession.refresh(changed = SessionManager.Session.Changed.Favorites)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
itemIsFavorited.value = !itemIsFavorited.value
|
|
||||||
onFavoriteChanged(itemIsFavorited.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shareListUrl(context: Context, listId: Int) {
|
private fun shareListUrl(context: Context, listId: Int) {
|
||||||
val shareUrl = "https://www.themoviedb.org/list/$listId"
|
val shareUrl = "https://www.themoviedb.org/list/$listId"
|
||||||
val sendIntent = Intent().apply {
|
val sendIntent = Intent().apply {
|
||||||
@@ -665,46 +565,4 @@ private fun shareListUrl(context: Context, listId: Int) {
|
|||||||
}
|
}
|
||||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||||
context.startActivity(shareIntent)
|
context.startActivity(shareIntent)
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchList(
|
|
||||||
itemId: Int,
|
|
||||||
service: ListV4Service,
|
|
||||||
listItem: MutableState<MediaList?>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = service.getList(itemId)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
listItem.value = response.body()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeItemFromList(
|
|
||||||
context: Context,
|
|
||||||
itemName: String,
|
|
||||||
itemId: Int,
|
|
||||||
itemType: MediaViewType,
|
|
||||||
service: ListV4Service,
|
|
||||||
list: MutableState<MediaList?>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val listId = list.value?.id ?: 0
|
|
||||||
val removeItem = DeleteListItemsItem(itemId, itemType)
|
|
||||||
val result = service.deleteListItems(listId, DeleteListItemsBody(listOf(removeItem)))
|
|
||||||
if (result.isSuccessful) {
|
|
||||||
SessionManager.currentSession.value?.refresh(SessionManager.Session.Changed.List)
|
|
||||||
service.getList(listId).body()?.let {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
list.value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Toast.makeText(context, "Successfully removed $itemName", Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Log.w("RemoveListItemError", result.toString())
|
|
||||||
Toast.makeText(context, "An error occurred!", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,17 +2,29 @@ package com.owenlejeune.tvtime.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.animation.rememberSplineBasedDecay
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SmallTopAppBar
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarScrollState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -20,43 +32,40 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailPerson
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
|
|
||||||
import com.owenlejeune.tvtime.ui.components.ContentCard
|
import com.owenlejeune.tvtime.ui.components.ContentCard
|
||||||
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
import com.owenlejeune.tvtime.ui.components.DetailHeader
|
||||||
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
import com.owenlejeune.tvtime.ui.components.ExpandableContentCard
|
||||||
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
import com.owenlejeune.tvtime.ui.components.ExternalIdsArea
|
||||||
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
import com.owenlejeune.tvtime.ui.components.TwoLineImageTextCard
|
||||||
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||||
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
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonDetailScreen(
|
fun PersonDetailScreen(
|
||||||
appNavController: NavController,
|
appNavController: NavController,
|
||||||
personId: Int?
|
personId: Int
|
||||||
) {
|
) {
|
||||||
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
mainViewModel.getById(personId, MediaViewType.PERSON)
|
||||||
|
mainViewModel.getExternalIds(personId, MediaViewType.PERSON)
|
||||||
|
}
|
||||||
|
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
|
|
||||||
val person = remember { mutableStateOf<DetailPerson?>(null) }
|
val peopleMap = remember { mainViewModel.peopleMap }
|
||||||
personId?.let {
|
val person = peopleMap[personId]
|
||||||
if (person.value == null) {
|
|
||||||
fetchPerson(personId, person)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
@@ -74,7 +83,7 @@ fun PersonDetailScreen(
|
|||||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||||
titleContentColor = MaterialTheme.colorScheme.primary
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
),
|
),
|
||||||
title = { Text(text = person.value?.name ?: "") },
|
title = { Text(text = person?.name ?: "") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = { appNavController.popBackStack() }) {
|
IconButton(onClick = { appNavController.popBackStack() }) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -98,34 +107,23 @@ fun PersonDetailScreen(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
DetailHeader(
|
DetailHeader(
|
||||||
posterUrl = TmdbUtils.getFullPersonImagePath(person.value?.profilePath),
|
posterUrl = TmdbUtils.getFullPersonImagePath(person?.profilePath),
|
||||||
posterContentDescription = person.value?.profilePath
|
posterContentDescription = person?.profilePath
|
||||||
)
|
)
|
||||||
|
|
||||||
BiographyCard(person = person.value)
|
BiographyCard(person = person)
|
||||||
|
|
||||||
val externalIds = remember { mutableStateOf<ExternalIds?>(null) }
|
val externalIdsMap = remember { mainViewModel.peopleExternalIdsMap }
|
||||||
LaunchedEffect(Unit) {
|
val externalIds = externalIdsMap[personId]
|
||||||
scope.launch {
|
externalIds?.let {
|
||||||
val response = PeopleService().getExternalIds(personId!!)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
externalIds.value = response.body()!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
externalIds.value?.let {
|
|
||||||
ExternalIdsArea(
|
ExternalIdsArea(
|
||||||
externalIds = it,
|
externalIds = it,
|
||||||
modifier = Modifier.padding(start = 4.dp)
|
modifier = Modifier.padding(start = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val credits = remember { mutableStateOf<PersonCreditsResponse?>(null) }
|
val creditsMap = remember { mainViewModel.peopleCastMap }
|
||||||
personId?.let {
|
val credits = creditsMap[personId]
|
||||||
if (credits.value == null) {
|
|
||||||
fetchCredits(personId, credits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentCard(
|
ContentCard(
|
||||||
title = stringResource(R.string.known_for_label)
|
title = stringResource(R.string.known_for_label)
|
||||||
@@ -137,8 +135,8 @@ fun PersonDetailScreen(
|
|||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
items(credits.value?.cast?.size ?: 0) { i ->
|
items(credits?.size ?: 0) { i ->
|
||||||
val content = credits.value!!.cast[i]
|
val content = credits!![i]
|
||||||
|
|
||||||
TwoLineImageTextCard(
|
TwoLineImageTextCard(
|
||||||
title = content.name,
|
title = content.name,
|
||||||
@@ -149,18 +147,18 @@ fun PersonDetailScreen(
|
|||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||||
onItemClicked = {
|
onItemClicked = {
|
||||||
personId?.let {
|
appNavController.navigate(
|
||||||
appNavController.navigate(
|
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
||||||
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val departments = credits.value?.crew?.map { it.department }?.toSet() ?: emptySet()
|
val crewMap = remember { mainViewModel.peopleCrewMap }
|
||||||
|
val crewCredits = crewMap[personId]
|
||||||
|
val departments = crewCredits?.map { it.department }?.toSet() ?: emptySet()
|
||||||
if (departments.isNotEmpty()) {
|
if (departments.isNotEmpty()) {
|
||||||
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
ContentCard(title = stringResource(R.string.also_known_for_label)) {
|
||||||
Column(
|
Column(
|
||||||
@@ -178,8 +176,7 @@ fun PersonDetailScreen(
|
|||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
val jobsInDepartment =
|
val jobsInDepartment = crewCredits!!.filter { it.department == department }
|
||||||
credits.value!!.crew.filter { it.department == department }
|
|
||||||
items(jobsInDepartment.size) { i ->
|
items(jobsInDepartment.size) { i ->
|
||||||
val content = jobsInDepartment[i]
|
val content = jobsInDepartment[i]
|
||||||
val title = if (content.mediaType == MediaViewType.MOVIE) {
|
val title = if (content.mediaType == MediaViewType.MOVIE) {
|
||||||
@@ -195,11 +192,9 @@ fun PersonDetailScreen(
|
|||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
imageUrl = TmdbUtils.getFullPosterPath(content.posterPath),
|
||||||
onItemClicked = {
|
onItemClicked = {
|
||||||
personId?.let {
|
appNavController.navigate(
|
||||||
appNavController.navigate(
|
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
||||||
AppNavItem.DetailView.withArgs(content.mediaType, content.id)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -228,26 +223,4 @@ private fun BiographyCard(person: DetailPerson?) {
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchPerson(id: Int, person: MutableState<DetailPerson?>) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val result = PeopleService().getPerson(id)
|
|
||||||
if (result.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
person.value = result.body()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchCredits(id: Int, credits: MutableState<PersonCreditsResponse?>) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val result = PeopleService().getCredits(id)
|
|
||||||
if (result.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
credits.value = result.body()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -17,22 +17,27 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.collectAsLazyPagingItems
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.DetailService
|
|
||||||
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.SearchService
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.SearchService
|
||||||
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.*
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
|
||||||
import com.owenlejeune.tvtime.extensions.listItems
|
import com.owenlejeune.tvtime.extensions.listItems
|
||||||
import com.owenlejeune.tvtime.ui.components.MediaResultCard
|
import com.owenlejeune.tvtime.ui.components.MediaResultCard
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
|
||||||
|
import com.owenlejeune.tvtime.ui.viewmodel.SearchViewModel
|
||||||
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
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.java.KoinJavaComponent.get
|
||||||
|
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchScreen(
|
fun SearchScreen(
|
||||||
@@ -40,6 +45,8 @@ fun SearchScreen(
|
|||||||
title: String,
|
title: String,
|
||||||
mediaViewType: MediaViewType
|
mediaViewType: MediaViewType
|
||||||
) {
|
) {
|
||||||
|
val searchViewModel = viewModel<SearchViewModel>()
|
||||||
|
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setStatusBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
systemUiController.setNavigationBarColor(color = MaterialTheme.colorScheme.background)
|
||||||
@@ -52,6 +59,14 @@ fun SearchScreen(
|
|||||||
val searchValue = rememberSaveable { mutableStateOf("") }
|
val searchValue = rememberSaveable { mutableStateOf("") }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(searchValue.value) {
|
||||||
|
if (searchValue.value.isEmpty()) {
|
||||||
|
searchViewModel.resetResults()
|
||||||
|
} else {
|
||||||
|
searchViewModel.searchFor(searchValue.value, mediaViewType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
title = {
|
title = {
|
||||||
TextField(
|
TextField(
|
||||||
@@ -100,68 +115,20 @@ fun SearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchValue.value.isNotEmpty()) {
|
when (mediaViewType) {
|
||||||
when (mediaViewType) {
|
MediaViewType.TV -> {
|
||||||
MediaViewType.TV -> {
|
TvResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
||||||
SearchResultListView(
|
|
||||||
showLoadingAnimation = showLoadingAnimation,
|
|
||||||
currentQuery = searchValue,
|
|
||||||
searchExecutor = { searchResults: MutableState<List<SearchResultTv>> ->
|
|
||||||
searchTv(searchValue.value, searchResults)
|
|
||||||
}
|
|
||||||
) { tv ->
|
|
||||||
TvSearchResultView(result = tv, appNavController = appNavController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MediaViewType.MOVIE -> {
|
|
||||||
SearchResultListView(
|
|
||||||
showLoadingAnimation = showLoadingAnimation,
|
|
||||||
currentQuery = searchValue,
|
|
||||||
searchExecutor = { searchResults: MutableState<List<SearchResultMovie>> ->
|
|
||||||
searchMovies(searchValue.value, searchResults)
|
|
||||||
}
|
|
||||||
) { movie ->
|
|
||||||
MovieSearchResultView(result = movie, appNavController = appNavController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MediaViewType.PERSON -> {
|
|
||||||
SearchResultListView(
|
|
||||||
showLoadingAnimation = showLoadingAnimation,
|
|
||||||
currentQuery = searchValue,
|
|
||||||
searchExecutor = { searchResults: MutableState<List<SearchResultPerson>> ->
|
|
||||||
searchPeople(searchValue.value, searchResults)
|
|
||||||
}
|
|
||||||
) { person ->
|
|
||||||
PeopleSearchResultView(result = person, appNavController = appNavController)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MediaViewType.MIXED -> {
|
|
||||||
SearchResultListView(
|
|
||||||
showLoadingAnimation = showLoadingAnimation,
|
|
||||||
currentQuery = searchValue,
|
|
||||||
searchExecutor = { searchResults: MutableState<List<SortableSearchResult>> ->
|
|
||||||
searchMulti(searchValue.value, searchResults)
|
|
||||||
},
|
|
||||||
) { item ->
|
|
||||||
when (item.mediaType) {
|
|
||||||
MediaViewType.MOVIE -> MovieSearchResultView(
|
|
||||||
appNavController = appNavController,
|
|
||||||
result = item as SearchResultMovie
|
|
||||||
)
|
|
||||||
MediaViewType.TV -> TvSearchResultView(
|
|
||||||
appNavController = appNavController,
|
|
||||||
result = item as SearchResultTv
|
|
||||||
)
|
|
||||||
MediaViewType.PERSON -> PeopleSearchResultView(
|
|
||||||
appNavController = appNavController,
|
|
||||||
result = item as SearchResultPerson
|
|
||||||
)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
|
MediaViewType.MOVIE -> {
|
||||||
|
MovieResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
||||||
|
}
|
||||||
|
MediaViewType.PERSON -> {
|
||||||
|
PeopleResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
||||||
|
}
|
||||||
|
MediaViewType.MIXED -> {
|
||||||
|
MultiResultsView(appNavController = appNavController, searchViewModel = searchViewModel)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = "") {
|
LaunchedEffect(key1 = "") {
|
||||||
@@ -171,41 +138,174 @@ fun SearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun <T: SortableSearchResult> SearchResultListView(
|
private fun MovieResultsView(
|
||||||
showLoadingAnimation: MutableState<Boolean>,
|
appNavController: NavHostController,
|
||||||
currentQuery: MutableState<String>,
|
searchViewModel: SearchViewModel
|
||||||
searchExecutor: (MutableState<List<T>>) -> Unit,
|
|
||||||
viewRenderer: @Composable (T) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val searchResults = remember { mutableStateOf(emptyList<T>()) }
|
val results = remember { searchViewModel.movieResults }
|
||||||
|
results.value?.let {
|
||||||
LaunchedEffect(key1 = currentQuery.value) {
|
val pagingItems = it.collectAsLazyPagingItems()
|
||||||
showLoadingAnimation.value = true
|
if (pagingItems.itemCount > 0) {
|
||||||
searchExecutor(searchResults)
|
LazyColumn(
|
||||||
showLoadingAnimation.value = false
|
modifier = Modifier.padding(all = 12.dp),
|
||||||
}
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
if (currentQuery.value.isNotEmpty() && searchResults.value.isEmpty()) {
|
lazyPagingItems(pagingItems) { item ->
|
||||||
Column(
|
item?.let {
|
||||||
modifier = Modifier.fillMaxSize()
|
MovieSearchResultView(
|
||||||
) {
|
appNavController = appNavController,
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
result = item
|
||||||
Text(
|
)
|
||||||
text = stringResource(R.string.no_search_results),
|
}
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
}
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
}
|
||||||
fontSize = 18.sp
|
} else {
|
||||||
)
|
Column(
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_search_results),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LazyColumn(
|
}
|
||||||
modifier = Modifier.padding(12.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
@Composable
|
||||||
) {
|
private fun TvResultsView(
|
||||||
val items = searchResults.value.sortedByDescending { it.popularity }
|
appNavController: NavHostController,
|
||||||
listItems(items) { item ->
|
searchViewModel: SearchViewModel
|
||||||
viewRenderer(item)
|
) {
|
||||||
|
val results = remember { searchViewModel.tvResults }
|
||||||
|
results.value?.let {
|
||||||
|
val pagingItems = it.collectAsLazyPagingItems()
|
||||||
|
if (pagingItems.itemCount > 0) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(all = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
lazyPagingItems(pagingItems) { item ->
|
||||||
|
item?.let {
|
||||||
|
TvSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_search_results),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PeopleResultsView(
|
||||||
|
appNavController: NavHostController,
|
||||||
|
searchViewModel: SearchViewModel
|
||||||
|
) {
|
||||||
|
val results = remember { searchViewModel.peopleResults }
|
||||||
|
results.value?.let {
|
||||||
|
val pagingItems = it.collectAsLazyPagingItems()
|
||||||
|
if (pagingItems.itemCount > 0) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(all = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
lazyPagingItems(pagingItems) { item ->
|
||||||
|
item?.let {
|
||||||
|
PeopleSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_search_results),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MultiResultsView(
|
||||||
|
appNavController: NavHostController,
|
||||||
|
searchViewModel: SearchViewModel
|
||||||
|
) {
|
||||||
|
val results = remember { searchViewModel.multiResults }
|
||||||
|
results.value?.let {
|
||||||
|
val pagingItems = it.collectAsLazyPagingItems()
|
||||||
|
if (pagingItems.itemCount > 0) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(all = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
lazyPagingItems(pagingItems) { item ->
|
||||||
|
item?.let {
|
||||||
|
when (item.mediaType) {
|
||||||
|
MediaViewType.MOVIE -> {
|
||||||
|
MovieSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultMovie
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MediaViewType.TV -> {
|
||||||
|
TvSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultTv
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MediaViewType.PERSON -> {
|
||||||
|
PeopleSearchResultView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
result = item as SearchResultPerson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else ->{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_search_results),
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,10 +333,15 @@ private fun <T: SortableSearchResult> SearchResultItemView(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MovieSearchResultView(
|
private fun MovieSearchResultView(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
result: SearchResultMovie
|
result: SearchResultMovie,
|
||||||
|
service: MoviesService = get(MoviesService::class.java)
|
||||||
) {
|
) {
|
||||||
val cast = remember { mutableStateOf<List<CastMember>?>(null) }
|
LaunchedEffect(Unit) {
|
||||||
getCast(result.id, MoviesService(), cast)
|
service.getCastAndCrew(result.id)
|
||||||
|
}
|
||||||
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
val castMap = remember { mainViewModel.movieCast }
|
||||||
|
val cast = castMap[result.id]
|
||||||
|
|
||||||
SearchResultItemView(
|
SearchResultItemView(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
@@ -247,7 +352,7 @@ private fun MovieSearchResultView(
|
|||||||
additionalDetails = {
|
additionalDetails = {
|
||||||
listOf(
|
listOf(
|
||||||
TmdbUtils.releaseYearFromData(result.releaseDate),
|
TmdbUtils.releaseYearFromData(result.releaseDate),
|
||||||
cast.value?.joinToString(separator = ", ") { it.name } ?: ""
|
cast?.joinToString(separator = ", ") { it.name } ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -256,12 +361,16 @@ private fun MovieSearchResultView(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TvSearchResultView(
|
private fun TvSearchResultView(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
result: SearchResultTv
|
result: SearchResultTv,
|
||||||
|
service: TvService = get(TvService::class.java)
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
val cast = remember { mutableStateOf<List<CastMember>?>(null) }
|
service.getCastAndCrew(result.id)
|
||||||
getCast(result.id, TvService(), cast)
|
}
|
||||||
|
val mainViewModel = viewModel<MainViewModel>()
|
||||||
|
val castMap = remember { mainViewModel.tvCast }
|
||||||
|
val cast = castMap[result.id]
|
||||||
|
|
||||||
SearchResultItemView(
|
SearchResultItemView(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
@@ -272,7 +381,7 @@ private fun TvSearchResultView(
|
|||||||
additionalDetails = {
|
additionalDetails = {
|
||||||
listOf(
|
listOf(
|
||||||
"${TmdbUtils.releaseYearFromData(result.releaseDate)} ${context.getString(R.string.search_result_tv_series)}",
|
"${TmdbUtils.releaseYearFromData(result.releaseDate)} ${context.getString(R.string.search_result_tv_series)}",
|
||||||
cast.value?.joinToString(separator = ", ") { it.name } ?: ""
|
cast?.joinToString(separator = ", ") { it.name } ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -297,78 +406,4 @@ private fun PeopleSearchResultView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchMovies(
|
|
||||||
query: String,
|
|
||||||
searchResults: MutableState<List<SearchResultMovie>>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = SearchService().searchMovies(query)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
searchResults.value = response.body()?.results ?: emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchTv(
|
|
||||||
query: String,
|
|
||||||
searchResults: MutableState<List<SearchResultTv>>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = SearchService().searchTv(query)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
searchResults.value = response.body()?.results ?: emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchPeople(
|
|
||||||
query: String,
|
|
||||||
searchResults: MutableState<List<SearchResultPerson>>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = SearchService().searchPeople(query)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
searchResults.value = response.body()?.results ?: emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchMulti(
|
|
||||||
query: String,
|
|
||||||
searchResults: MutableState<List<SortableSearchResult>>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = SearchService().searchMulti(query)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
searchResults.value = response.body()?.results ?: emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCast(
|
|
||||||
id: Int,
|
|
||||||
detailService: DetailService,
|
|
||||||
cast: MutableState<List<CastMember>?>
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val response = detailService.getCastAndCrew(id)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
cast.value = response.body()?.cast?.let {
|
|
||||||
val end = minOf(2, it.size)
|
|
||||||
it.subList(0, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
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.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.utils.SessionManager
|
||||||
|
import com.owenlejeune.tvtime.utils.types.MediaViewType
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class AccountViewModel: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val listService: ListV4Service by inject()
|
||||||
|
private val accountService: AccountService by inject()
|
||||||
|
|
||||||
|
val listMap = listService.listMap
|
||||||
|
|
||||||
|
suspend fun getList(listId: Int) {
|
||||||
|
listService.getList(listId = listId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteListItem(listId: Int, itemId: Int, itemType: MediaViewType) {
|
||||||
|
val removeItemBody = DeleteListItemsItem(itemId, itemType)
|
||||||
|
listService.deleteListItems(listId, DeleteListItemsBody(listOf(removeItemBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateList(listId: Int, body: ListUpdateBody) {
|
||||||
|
listService.updateList(listId, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addToFavourites(type: MediaViewType, itemId: Int, favourited: Boolean) {
|
||||||
|
val accountId = SessionManager.currentSession.value?.accountDetails?.value?.id ?: throw Exception("Session must not be null")
|
||||||
|
accountService.markAsFavorite(accountId, MarkAsFavoriteBody(type, itemId, favourited))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addToWatchlist(type: MediaViewType, itemId: Int, watchlisted: Boolean) {
|
||||||
|
val accountId = SessionManager.currentSession.value?.accountDetails?.value?.id ?: throw Exception("Session must not be null")
|
||||||
|
accountService.addToWatchlist(accountId, WatchlistBody(type, itemId, watchlisted))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,125 +1,28 @@
|
|||||||
package com.owenlejeune.tvtime.ui.viewmodel
|
package com.owenlejeune.tvtime.ui.viewmodel
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.ConfigurationApi
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.ConfigurationService
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationCountry
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationDetails
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationJob
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationLanguage
|
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ConfigurationTimezone
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
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 ConfigurationViewModel: ViewModel(), KoinComponent {
|
class ConfigurationViewModel: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
companion object {
|
private val service: ConfigurationService by inject()
|
||||||
private const val TAG = "ConfigurationViewModel"
|
|
||||||
}
|
|
||||||
|
|
||||||
private object Backer {
|
val detailsConfiguration = service.detailsConfiguration
|
||||||
val detailsConfiguration = mutableStateOf(ConfigurationDetails.Empty)
|
val countriesConfiguration = service.countriesConfiguration
|
||||||
val countriesConfiguration = mutableStateListOf<ConfigurationCountry>()
|
val jobsConfiguration = service.jobsConfiguration
|
||||||
val jobsConfiguration = mutableStateListOf<ConfigurationJob>()
|
val languagesConfiguration = service.languagesConfiguration
|
||||||
val languagesConfiguration = mutableStateListOf<ConfigurationLanguage>()
|
val primaryTranslationsConfiguration = service.primaryTranslationsConfiguration
|
||||||
val primaryTranslationsConfiguration = mutableStateListOf<String>()
|
val timezonesConfiguration = service.timezonesConfiguration
|
||||||
val timezonesConfiguration = mutableStateListOf<ConfigurationTimezone>()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val service: ConfigurationApi by inject()
|
|
||||||
|
|
||||||
val detailsConfiguration = Backer.detailsConfiguration
|
|
||||||
val countriesConfiguration = Backer.countriesConfiguration
|
|
||||||
val jobsConfiguration = Backer.jobsConfiguration
|
|
||||||
val languagesConfiguration = Backer.languagesConfiguration
|
|
||||||
val primaryTranslationsConfiguration = Backer.primaryTranslationsConfiguration
|
|
||||||
val timezonesConfiguration = Backer.timezonesConfiguration
|
|
||||||
|
|
||||||
suspend fun getConfigurations() {
|
suspend fun getConfigurations() {
|
||||||
getDetailsConfiguration()
|
service.getDetailsConfiguration()
|
||||||
getCountriesConfiguration()
|
service.getCountriesConfiguration()
|
||||||
getJobsConfiguration()
|
service.getJobsConfiguration()
|
||||||
getLanguagesConfiguration()
|
service.getLanguagesConfiguration()
|
||||||
getPrimaryTranslationsConfiguration()
|
service.getPrimaryTranslationsConfiguration()
|
||||||
getTimezonesConfiguration()
|
service.getTimezonesConfiguration()
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getDetailsConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getDetailsConfiguration() },
|
|
||||||
{ detailsConfiguration.value = it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getCountriesConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getCountriesConfiguration() },
|
|
||||||
{
|
|
||||||
countriesConfiguration.clear()
|
|
||||||
countriesConfiguration.addAll(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getJobsConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getJobsConfiguration() },
|
|
||||||
{
|
|
||||||
jobsConfiguration.clear()
|
|
||||||
jobsConfiguration.addAll(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getLanguagesConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getLanguagesConfiguration() },
|
|
||||||
{
|
|
||||||
languagesConfiguration.clear()
|
|
||||||
languagesConfiguration.addAll(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getPrimaryTranslationsConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getPrimaryTranslationsConfiguration() },
|
|
||||||
{
|
|
||||||
primaryTranslationsConfiguration.clear()
|
|
||||||
primaryTranslationsConfiguration.addAll(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getTimezonesConfiguration() {
|
|
||||||
getConfiguration(
|
|
||||||
{ service.getTimezonesConfiguration() },
|
|
||||||
{
|
|
||||||
timezonesConfiguration.clear()
|
|
||||||
timezonesConfiguration.addAll(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun <T> getConfiguration(
|
|
||||||
fetcher: suspend () -> Response<T>,
|
|
||||||
bodyHandler: (T) -> Unit
|
|
||||||
) {
|
|
||||||
val response = fetcher()
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
response.body()?.let {
|
|
||||||
Log.d(TAG, "Successfully got configuration: $it")
|
|
||||||
bodyHandler(it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Issue getting configuration")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
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.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
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.CrewMember
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.DetailedItem
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Keyword
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
||||||
|
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.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
|
||||||
|
|
||||||
|
class MainViewModel: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val movieService: MoviesService by inject()
|
||||||
|
private val tvService: TvService by inject()
|
||||||
|
private val peopleService: PeopleService by inject()
|
||||||
|
|
||||||
|
val detailMovies = movieService.detailMovies
|
||||||
|
val movieImages = movieService.images
|
||||||
|
val movieCast = movieService.cast
|
||||||
|
val movieCrew = movieService.crew
|
||||||
|
val movieVideos = movieService.videos
|
||||||
|
val movieReviews = movieService.reviews
|
||||||
|
val movieKeywords = movieService.keywords
|
||||||
|
val movieWatchProviders = movieService.watchProviders
|
||||||
|
val movieExternalIds = movieService.externalIds
|
||||||
|
val movieReleaseDates = movieService.releaseDates
|
||||||
|
val similarMovies = movieService.similar
|
||||||
|
val movieAccountStates = movieService.accountStates
|
||||||
|
|
||||||
|
val detailedTv = tvService.detailTv
|
||||||
|
val tvImages = tvService.images
|
||||||
|
val tvCast = tvService.cast
|
||||||
|
val tvCrew = tvService.crew
|
||||||
|
val tvVideos = tvService.videos
|
||||||
|
val tvReviews = tvService.reviews
|
||||||
|
val tvKeywords = tvService.keywords
|
||||||
|
val tvWatchProviders = tvService.watchProviders
|
||||||
|
val tvExternalIds = tvService.externalIds
|
||||||
|
val tvContentRatings = tvService.contentRatings
|
||||||
|
val tvSeasons = tvService.seasons
|
||||||
|
val similarTv = tvService.similar
|
||||||
|
val tvAccountStates = tvService.accountStates
|
||||||
|
|
||||||
|
val peopleMap = peopleService.peopleMap
|
||||||
|
val peopleCastMap = peopleService.castMap
|
||||||
|
val peopleCrewMap = peopleService.crewMap
|
||||||
|
val peopleImagesMap = peopleService.imagesMap
|
||||||
|
val peopleExternalIdsMap = peopleService.externalIdsMap
|
||||||
|
|
||||||
|
private fun <T> providesForType(type: MediaViewType, movies: () -> T, tv: () -> T): T {
|
||||||
|
return when (type) {
|
||||||
|
MediaViewType.MOVIE -> movies()
|
||||||
|
MediaViewType.TV -> tv()
|
||||||
|
else -> throw ViewableMediaTypeException(type) // shouldn't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceDetailsFor(type: MediaViewType): Map<Int, DetailedItem> {
|
||||||
|
return providesForType(type, { detailMovies }, { detailedTv })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceImagesFor(type: MediaViewType): Map<Int, ImageCollection> {
|
||||||
|
return providesForType(type, { movieImages }, { tvImages })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceExternalIdsFor(type: MediaViewType): Map<Int, ExternalIds> {
|
||||||
|
return providesForType(type, { movieExternalIds }, { tvExternalIds })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceKeywordsFor(type: MediaViewType): Map<Int, List<Keyword>> {
|
||||||
|
return providesForType(type, { movieKeywords }, { tvKeywords })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceCastFor(type: MediaViewType): Map<Int, List<CastMember>> {
|
||||||
|
return providesForType(type, { movieCast }, { tvCast })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceCrewFor(type: MediaViewType): Map<Int, List<CrewMember>> {
|
||||||
|
return providesForType(type, { movieCrew }, { tvCrew })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceVideosFor(type: MediaViewType): Map<Int, List<Video>> {
|
||||||
|
return providesForType(type, { movieVideos }, { tvVideos })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceWatchProvidersFor(type: MediaViewType): Map<Int, WatchProviders> {
|
||||||
|
return providesForType(type, { movieWatchProviders }, { tvWatchProviders })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceReviewsFor(type: MediaViewType): Map<Int, List<Review>> {
|
||||||
|
return providesForType(type, { movieReviews }, { tvReviews })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceSimilarContentFor(type: MediaViewType): Map<Int, Flow<PagingData<TmdbItem>>> {
|
||||||
|
return providesForType(type, { similarMovies }, { similarTv })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun produceAccountStatesFor(type: MediaViewType): Map<Int, AccountStates> {
|
||||||
|
return providesForType(type, { movieAccountStates }, { tvAccountStates} )
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getById(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getById(id)
|
||||||
|
MediaViewType.TV -> tvService.getById(id)
|
||||||
|
MediaViewType.PERSON -> peopleService.getPerson(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getImages(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getImages(id)
|
||||||
|
MediaViewType.TV -> tvService.getImages(id)
|
||||||
|
MediaViewType.PERSON -> peopleService.getImages(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCastAndCrew(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getCastAndCrew(id)
|
||||||
|
MediaViewType.TV -> tvService.getCastAndCrew(id)
|
||||||
|
MediaViewType.PERSON -> tvService.getCastAndCrew(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getVideos(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getVideos(id)
|
||||||
|
MediaViewType.TV -> tvService.getVideos(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReviews(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getReviews(id)
|
||||||
|
MediaViewType.TV -> tvService.getReviews(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getKeywords(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getKeywords(id)
|
||||||
|
MediaViewType.TV -> tvService.getKeywords(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getWatchProviders(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getWatchProviders(id)
|
||||||
|
MediaViewType.TV -> tvService.getWatchProviders(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getExternalIds(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getExternalIds(id)
|
||||||
|
MediaViewType.TV -> tvService.getExternalIds(id)
|
||||||
|
MediaViewType.PERSON -> peopleService.getExternalIds(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAccountStates(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.getAccountStates(id)
|
||||||
|
MediaViewType.TV -> tvService.getAccountStates(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun postRating(id: Int, rating: Float, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.postRating(id, RatingBody(rating))
|
||||||
|
MediaViewType.TV -> tvService.postRating(id, RatingBody(rating))
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteRating(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> movieService.deleteRating(id)
|
||||||
|
MediaViewType.TV -> tvService.deleteRating(id)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSimilar(id: Int, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> {
|
||||||
|
similarMovies[id] = Pager(PagingConfig(pageSize = 1)) {
|
||||||
|
SimilarMoviesSource(id)
|
||||||
|
}.flow.cachedIn(viewModelScope)
|
||||||
|
}
|
||||||
|
MediaViewType.TV -> {
|
||||||
|
similarTv[id] = Pager(PagingConfig(pageSize = 1)) {
|
||||||
|
SimilarTvSource(id)
|
||||||
|
}.flow.cachedIn(viewModelScope)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReleaseDates(id: Int) {
|
||||||
|
movieService.getReleaseDates(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getContentRatings(id: Int) {
|
||||||
|
tvService.getContentRatings(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeason(seriesId: Int, seasonId: Int) {
|
||||||
|
tvService.getSeason(seriesId, seasonId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,20 +13,22 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePagingSource
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||||
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
|
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
|
||||||
import kotlinx.coroutines.flow.Flow
|
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() {
|
sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun, tag: String): ViewModel(), KoinComponent {
|
||||||
|
|
||||||
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
|
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
|
||||||
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun, tag = tag)
|
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun, tag = tag)
|
||||||
}.flow.cachedIn(viewModelScope)
|
}.flow.cachedIn(viewModelScope)
|
||||||
|
|
||||||
object PopularMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getPopular(p) }, PopularMoviesVM::class.java.simpleName)
|
object PopularMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getPopular(p) }, PopularMoviesVM::class.java.simpleName)
|
||||||
object TopRatedMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getTopRated(p) }, TopRatedMoviesVM::class.java.simpleName)
|
object TopRatedMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getTopRated(p) }, TopRatedMoviesVM::class.java.simpleName)
|
||||||
object NowPlayingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getNowPlaying(p) }, NowPlayingMoviesVM::class.java.simpleName)
|
object NowPlayingMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getNowPlaying(p) }, NowPlayingMoviesVM::class.java.simpleName)
|
||||||
object UpcomingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getUpcoming(p) }, UpcomingMoviesVM::class.java.simpleName)
|
object UpcomingMoviesVM: MediaTabViewModel(get(MoviesService::class.java), { s, p -> s.getUpcoming(p) }, UpcomingMoviesVM::class.java.simpleName)
|
||||||
object PopularTvVM: MediaTabViewModel(TvService(), { s, p -> s.getPopular(p) }, PopularTvVM::class.java.simpleName)
|
object PopularTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getPopular(p) }, PopularTvVM::class.java.simpleName)
|
||||||
object TopRatedTvVM: MediaTabViewModel(TvService(), { s, p -> s.getTopRated(p) }, TopRatedTvVM::class.java.simpleName)
|
object TopRatedTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getTopRated(p) }, TopRatedTvVM::class.java.simpleName)
|
||||||
object AiringTodayTvVM: MediaTabViewModel(TvService(), { s, p -> s.getNowPlaying(p) }, AiringTodayTvVM::class.java.simpleName)
|
object AiringTodayTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getNowPlaying(p) }, AiringTodayTvVM::class.java.simpleName)
|
||||||
object OnTheAirTvVM: MediaTabViewModel(TvService(), { s, p -> s.getUpcoming(p) }, OnTheAirTvVM::class.java.simpleName)
|
object OnTheAirTvVM: MediaTabViewModel(get(TvService::class.java), { s, p -> s.getUpcoming(p) }, OnTheAirTvVM::class.java.simpleName)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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.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
|
||||||
|
|
||||||
|
class SearchViewModel: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val service: SearchService by inject()
|
||||||
|
|
||||||
|
val movieResults = service.movieResults
|
||||||
|
val tvResults = service.tvResults
|
||||||
|
val peopleResults = service.peopleResults
|
||||||
|
val multiResults = service.multiResults
|
||||||
|
|
||||||
|
fun resetResults() {
|
||||||
|
movieResults.value = null
|
||||||
|
tvResults.value = null
|
||||||
|
peopleResults.value = null
|
||||||
|
multiResults.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchFor(query: String, type: MediaViewType) {
|
||||||
|
when (type) {
|
||||||
|
MediaViewType.MOVIE -> searchForMovies(query)
|
||||||
|
MediaViewType.TV -> searchForTv(query)
|
||||||
|
MediaViewType.PERSON -> searchForPeople(query)
|
||||||
|
MediaViewType.MIXED -> searchMulti(query)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchForMovies(query: String) {
|
||||||
|
movieResults.value = createPagingSource(viewModelScope) { service.searchMovies(query, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchForTv(query: String) {
|
||||||
|
tvResults.value = createPagingSource(viewModelScope) { service.searchTv(query, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchForPeople(query: String) {
|
||||||
|
peopleResults.value = createPagingSource(viewModelScope) { service.searchPeople(query, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchMulti(query: String) {
|
||||||
|
multiResults.value = createPagingSource(viewModelScope) { 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ 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.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.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.AccountV4Service
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
|
||||||
@@ -30,12 +31,11 @@ import java.nio.charset.StandardCharsets
|
|||||||
object SessionManager: KoinComponent {
|
object SessionManager: KoinComponent {
|
||||||
|
|
||||||
private val preferences: AppPreferences by inject()
|
private val preferences: AppPreferences by inject()
|
||||||
|
private val authenticationService: AuthenticationService by inject()
|
||||||
|
private val authenticationV4Service: AuthenticationV4Service by inject()
|
||||||
|
|
||||||
val currentSession = mutableStateOf<Session?>(null)
|
val currentSession = mutableStateOf<Session?>(null)
|
||||||
|
|
||||||
private val authenticationService by lazy { TmdbClient().createAuthenticationService() }
|
|
||||||
private val authenticationV4Service by lazy { TmdbClient().createV4AuthenticationService() }
|
|
||||||
|
|
||||||
class AuthorizedSessionValues(
|
class AuthorizedSessionValues(
|
||||||
@SerializedName("session_id") val sessionId: String,
|
@SerializedName("session_id") val sessionId: String,
|
||||||
@SerializedName("access_token") val accessToken: String,
|
@SerializedName("access_token") val accessToken: String,
|
||||||
@@ -132,50 +132,15 @@ 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 = "") {
|
||||||
// protected open var _ratedMovies: List<RatedMovie> = emptyList()
|
|
||||||
// val ratedMovies: List<RatedMovie>
|
|
||||||
// get() = _ratedMovies
|
|
||||||
|
|
||||||
val ratedMovies = mutableStateListOf<RatedMovie>()
|
val ratedMovies = mutableStateListOf<RatedMovie>()
|
||||||
|
|
||||||
// protected open var _ratedTvShows: List<RatedTv> = emptyList()
|
|
||||||
// val ratedTvShows: List<RatedTv>
|
|
||||||
// get() = _ratedTvShows
|
|
||||||
val ratedTvShows = mutableStateListOf<RatedTv>()
|
val ratedTvShows = mutableStateListOf<RatedTv>()
|
||||||
|
|
||||||
// protected open var _ratedTvEpisodes: List<RatedEpisode> = emptyList()
|
|
||||||
// val ratedTvEpisodes: List<RatedEpisode>
|
|
||||||
// get() = _ratedTvEpisodes
|
|
||||||
val ratedTvEpisodes = mutableStateListOf<RatedEpisode>()
|
val ratedTvEpisodes = mutableStateListOf<RatedEpisode>()
|
||||||
|
|
||||||
// protected open var _accountDetails: AccountDetails? = null
|
|
||||||
// val accountDetails: AccountDetails?
|
|
||||||
// get() = _accountDetails
|
|
||||||
val accountDetails = mutableStateOf<AccountDetails?>(null)
|
val accountDetails = mutableStateOf<AccountDetails?>(null)
|
||||||
|
|
||||||
// protected open var _accountLists: List<V4AccountList> = emptyList()
|
|
||||||
// val accountLists: List<V4AccountList>
|
|
||||||
// get() = _accountLists
|
|
||||||
val accountLists = mutableStateListOf<AccountList>()
|
val accountLists = mutableStateListOf<AccountList>()
|
||||||
|
|
||||||
// protected open var _favoriteMovies: List<FavoriteMovie> = emptyList()
|
|
||||||
// val favoriteMovies: List<FavoriteMovie>
|
|
||||||
// get() = _favoriteMovies
|
|
||||||
val favoriteMovies = mutableStateListOf<FavoriteMovie>()
|
val favoriteMovies = mutableStateListOf<FavoriteMovie>()
|
||||||
|
|
||||||
// protected open var _favoriteTvShows: List<FavoriteTvSeries> = emptyList()
|
|
||||||
// val favoriteTvShows: List<FavoriteTvSeries>
|
|
||||||
// get() = _favoriteTvShows
|
|
||||||
val favoriteTvShows = mutableStateListOf<FavoriteTvSeries>()
|
val favoriteTvShows = mutableStateListOf<FavoriteTvSeries>()
|
||||||
|
|
||||||
// protected open var _movieWatchlist: List<WatchlistMovie> = emptyList()
|
|
||||||
// val movieWatchlist: List<WatchlistMovie>
|
|
||||||
// get() = _movieWatchlist
|
|
||||||
val movieWatchlist = mutableStateListOf<WatchlistMovie>()
|
val movieWatchlist = mutableStateListOf<WatchlistMovie>()
|
||||||
|
|
||||||
// protected open var _tvWatchlist: List<WatchlistTvSeries> = emptyList()
|
|
||||||
// val tvWatchlist: List<WatchlistTvSeries>
|
|
||||||
// get() = _tvWatchlist
|
|
||||||
val tvWatchlist = mutableStateListOf<WatchlistTvSeries>()
|
val tvWatchlist = mutableStateListOf<WatchlistTvSeries>()
|
||||||
|
|
||||||
fun hasRatedMovie(id: Int): Boolean {
|
fun hasRatedMovie(id: Int): Boolean {
|
||||||
|
|||||||
@@ -145,14 +145,14 @@ object TmdbUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMovieRating(releases: MovieReleaseResults?): String {
|
fun getMovieRating(releases: List<MovieReleaseResults.ReleaseDateResult>?): String {
|
||||||
if (releases == null) {
|
if (releases == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentRegion = Locale.current.language
|
val currentRegion = Locale.current.language
|
||||||
val certifications = HashMap<String, String>()
|
val certifications = HashMap<String, String>()
|
||||||
releases.releaseDates.forEach { releaseDateResult ->
|
releases.forEach { releaseDateResult ->
|
||||||
if (releaseDateResult.region == currentRegion || releaseDateResult.region == DEF_REGION) {
|
if (releaseDateResult.region == currentRegion || releaseDateResult.region == DEF_REGION) {
|
||||||
val cert = releaseDateResult.releaseDates.firstOrNull { it.certification.isNotEmpty() }
|
val cert = releaseDateResult.releaseDates.firstOrNull { it.certification.isNotEmpty() }
|
||||||
if (cert != null) {
|
if (cert != null) {
|
||||||
@@ -166,14 +166,14 @@ object TmdbUtils {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTvRating(contentRatings: TvContentRatings?): String {
|
fun getTvRating(contentRatings: List<TvContentRatings.TvContentRating>?): String {
|
||||||
if (contentRatings == null) {
|
if (contentRatings == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentRegion = Locale.current.language
|
val currentRegion = Locale.current.language
|
||||||
val certifications = HashMap<String, String>()
|
val certifications = HashMap<String, String>()
|
||||||
contentRatings.results.forEach { contentRating ->
|
contentRatings.forEach { contentRating ->
|
||||||
if (contentRating.language == currentRegion || contentRating.language == DEF_REGION) {
|
if (contentRating.language == currentRegion || contentRating.language == DEF_REGION) {
|
||||||
certifications[contentRating.language] = contentRating.rating
|
certifications[contentRating.language] = contentRating.rating
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,6 @@ enum class MediaViewType {
|
|||||||
return values()[oridinal]
|
return values()[oridinal]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ViewableMediaTypeException(type: MediaViewType): IllegalArgumentException("Media type given: ${type}, \n expected one of MediaViewType.MOVIE, MediaViewType.TV")
|
||||||
Reference in New Issue
Block a user