diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/Client.kt b/app/src/main/java/com/owenlejeune/tvtime/api/Client.kt index d98cd56..c69a584 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/Client.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/Client.kt @@ -7,7 +7,7 @@ import retrofit2.Retrofit class Client(baseUrl: String): KoinComponent { - private val converter: Converter by inject() + private val converter: ConverterFactoryFactory by inject() private val client: HttpClient by inject() private var retrofit: Retrofit = Retrofit.Builder() diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/Converter.kt b/app/src/main/java/com/owenlejeune/tvtime/api/ConverterFactoryFactory.kt similarity index 73% rename from app/src/main/java/com/owenlejeune/tvtime/api/Converter.kt rename to app/src/main/java/com/owenlejeune/tvtime/api/ConverterFactoryFactory.kt index fa1a985..aa6bb48 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/Converter.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/ConverterFactoryFactory.kt @@ -2,7 +2,7 @@ package com.owenlejeune.tvtime.api import retrofit2.Converter -interface Converter { +interface ConverterFactoryFactory { fun get(): Converter.Factory diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/GsonConverter.kt b/app/src/main/java/com/owenlejeune/tvtime/api/GsonConverter.kt index cabc335..34000db 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/GsonConverter.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/GsonConverter.kt @@ -4,17 +4,14 @@ import com.google.gson.GsonBuilder import com.google.gson.JsonDeserializer import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import retrofit2.Converter import retrofit2.converter.gson.GsonConverterFactory -import kotlin.reflect.KClass -class GsonConverter: Converter, KoinComponent { +class GsonConverter: ConverterFactoryFactory, KoinComponent { private val deserializers: Map, JsonDeserializer<*>> by inject() - override fun get(): retrofit2.Converter.Factory { -// val gson = GsonBuilder() -// .registerTypeAdapter() -// .create() + override fun get(): Converter.Factory { val builder = GsonBuilder() deserializers.forEach { deserializer -> diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt index 270036f..9efc94c 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt @@ -59,6 +59,10 @@ class TmdbClient: KoinComponent { return client.create(AccountApi::class.java) } + fun createSearchService(): SearchApi { + return client.create(SearchApi::class.java) + } + private inner class TmdbInterceptor: Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val apiParam = QueryParam("api_key", BuildConfig.TMDB_ApiKey) diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchApi.kt new file mode 100644 index 0000000..8e5c2de --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchApi.kt @@ -0,0 +1,29 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3 + +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Collection +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface SearchApi { + + @GET("search/company") + suspend fun searchCompanies(@Query("query") query: String, @Query("page") page: Int): Response> + + @GET("search/collection") + suspend fun searchCollections(@Query("query") query: String, @Query("page") page: Int): Response> + + @GET("search/keyword") + suspend fun searchKeywords(@Query("query") query: String, @Query("page") page: Int): Response> + + @GET("search/movie") + suspend fun searchMovies(@Query("query") query: String, @Query("page") page: Int): Response> + + @GET("search/tv") + suspend fun searchTv(@Query("query") query: String, @Query("page") page: Int): Response> + + @GET("search/person") + suspend fun searchPeople(@Query("query") query: String, @Query("page") page: Int): Response> + +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchService.kt new file mode 100644 index 0000000..43da2e1 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/SearchService.kt @@ -0,0 +1,113 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3 + +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Collection +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import retrofit2.Response +import java.util.* + +class SearchService: KoinComponent { + + private val service: SearchApi by inject() + + suspend fun searchCompanies(query: String, page: Int = 1): Response> { + return service.searchCompanies(query, page) + } + + suspend fun searchCollections(query: String, page: Int = 1): Response> { + return service.searchCollections(query, page) + } + + suspend fun searchKeywords(query: String, page: Int = 1): Response> { + return service.searchKeywords(query, page) + } + + suspend fun searchMovies(query: String, page: Int = 1): Response> { + return service.searchMovies(query, page) + } + + suspend fun searchTv(query: String, page: Int = 1): Response> { + return service.searchTv(query, page) + } + + suspend fun searchPeople(query: String, page: Int = 1): Response> { + return service.searchPeople(query, page) + } + + suspend fun searchAll(query: String, onResultsUpdated: (List) -> Unit) { + val results = TreeSet {a, b -> + when (a) { + is Keyword -> { + if (b is Keyword) 0 else -3 + } + is ProductionCompany -> { + if (b is ProductionCompany) 0 else -2 + } + is Collection -> { + if (b is Collection) 0 else -1 + } + is SortableSearchResult -> { + when (b) { + is SortableSearchResult -> { + when { + a.popularity > b.popularity -> 1 + a.popularity < b.popularity -> -1 + else -> 0 + } + } + else -> 3 + } + } + else -> 0 + } + } + CoroutineScope(Dispatchers.IO).launch { + searchMovies(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + CoroutineScope(Dispatchers.IO).launch { + searchTv(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + CoroutineScope(Dispatchers.IO).launch { + searchPeople(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + CoroutineScope(Dispatchers.IO).launch { + searchCompanies(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + CoroutineScope(Dispatchers.IO).launch { + searchCollections(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + CoroutineScope(Dispatchers.IO).launch { + searchKeywords(query).body()?.apply { + withContext(Dispatchers.Main) { + results.addAll(results) + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/deserializer/KnownForDeserializer.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/deserializer/KnownForDeserializer.kt new file mode 100644 index 0000000..cb6da5d --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/deserializer/KnownForDeserializer.kt @@ -0,0 +1,34 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer + +import com.google.gson.* +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor +import com.owenlejeune.tvtime.ui.screens.MediaViewType +import java.lang.reflect.Type + +class KnownForDeserializer: JsonDeserializer { + + companion object { + const val MEDIA_TYPE = "media_type" + } + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext? + ): KnownFor { + if (json?.isJsonObject == true) { + val obj = json.asJsonObject + if (obj.has(MEDIA_TYPE)) { + val typeStr = obj.get(MEDIA_TYPE).asString + return when (Gson().fromJson(typeStr, MediaViewType::class.java)) { + MediaViewType.MOVIE -> Gson().fromJson(obj.toString(), KnownFor::class.java) + MediaViewType.TV -> Gson().fromJson(obj.toString(), KnownFor::class.java) + else -> throw JsonParseException("Not a valid MediaViewType: $typeStr") + } + } + throw JsonParseException("JSON object has no property $MEDIA_TYPE") + } + throw JsonParseException("Not a JSON object") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnowForTv.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnowForTv.kt new file mode 100644 index 0000000..fb17ccb --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnowForTv.kt @@ -0,0 +1,35 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName +import com.owenlejeune.tvtime.ui.screens.MediaViewType + +class KnowForTv( + backdropPath: String?, + releaseDate: String, + genreIds: List, + id: Int, + mediaType: MediaViewType, + title: String, + originalLanguage: String, + originalTitle: String, + overview: String, + popularity: Float, + posterPath: String?, + voteAverage: Float, + voteCount: Int, + @SerializedName("original_country") val originalCountries: List +) : KnownFor( + backdropPath, + releaseDate, + genreIds, + id, + mediaType, + title, + originalLanguage, + originalTitle, + overview, + popularity, + posterPath, + voteAverage, + voteCount +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownFor.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownFor.kt new file mode 100644 index 0000000..37cbb6c --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownFor.kt @@ -0,0 +1,20 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName +import com.owenlejeune.tvtime.ui.screens.MediaViewType + +abstract class KnownFor( + @SerializedName("backdrop_path") val backdropPath: String?, + @SerializedName("release_date", alternate = ["first_air_date"]) val releaseDate: String, + @SerializedName("genre_ids") val genreIds: List, + @SerializedName("id") val id: Int, + @SerializedName("media_type") val mediaType: MediaViewType, + @SerializedName("title", alternate = ["name"]) val title: String, + @SerializedName("original_language") val originalLanguage: String, + @SerializedName("original_title", alternate = ["original_name"]) val originalTitle: String, + @SerializedName("overview") val overview: String, + @SerializedName("popularity") val popularity: Float, + @SerializedName("poster_path") val posterPath: String?, + @SerializedName("vote_average") val voteAverage: Float, + @SerializedName("vote_count") val voteCount: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownForMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownForMovie.kt new file mode 100644 index 0000000..8f49e17 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/KnownForMovie.kt @@ -0,0 +1,36 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName +import com.owenlejeune.tvtime.ui.screens.MediaViewType + +class KnownForMovie( + backdropPath: String?, + releaseDate: String, + genreIds: List, + id: Int, + mediaType: MediaViewType, + title: String, + originalLanguage: String, + originalTitle: String, + overview: String, + popularity: Float, + posterPath: String?, + voteAverage: Float, + voteCount: Int, + @SerializedName("video") val isVideo: Boolean, + @SerializedName("adult") val isAdult: Boolean +) : KnownFor( + backdropPath, + releaseDate, + genreIds, + id, + mediaType, + title, + originalLanguage, + originalTitle, + overview, + popularity, + posterPath, + voteAverage, + voteCount +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/ProductionCompany.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/ProductionCompany.kt index cc548c2..47dacec 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/ProductionCompany.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/ProductionCompany.kt @@ -6,5 +6,5 @@ data class ProductionCompany( @SerializedName("id") val id: Int, @SerializedName("name") val name: String, @SerializedName("logo_path") val logoPath: String?, - @SerializedName("origin_country") val originCountry: String + @SerializedName("origin_country") val originCountry: String? ) diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResult.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResult.kt new file mode 100644 index 0000000..c90fde7 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResult.kt @@ -0,0 +1,10 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +abstract class SearchResult ( + @SerializedName("page") val page: Int, + @SerializedName("total_pages") val totalPages: Int, + @SerializedName("total_results") val totalResults: Int, + @SerializedName("results") val results: List +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMedia.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMedia.kt new file mode 100644 index 0000000..2d11c63 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMedia.kt @@ -0,0 +1,24 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +abstract class SearchResultMedia( + var type: SearchResultType, + @SerializedName("id") val id: Int, + @SerializedName("overview") val overview: String, + @SerializedName("name", alternate = ["title"]) val name: String, + @SerializedName("vote_average") val voteAverage: Float, + @SerializedName("vote_count") val voteCount: Int, + @SerializedName("release_date", alternate = ["first_air_date", "air_date"]) val releaseDate: String, + @SerializedName("backdrop_path") val backdropPath: String?, + @SerializedName("genre_ids") val genreIds: List, + @SerializedName("original_language") val originalLanguage: String, + @SerializedName("original_name", alternate = ["original_title"]) val originalName: String, + @SerializedName("poster_path") val posterPath: String?, + popularity: Float +): SortableSearchResult(popularity) { + enum class SearchResultType { + MOVIE, + TV + } +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMovie.kt new file mode 100644 index 0000000..a342e83 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultMovie.kt @@ -0,0 +1,23 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +class SearchResultMovie( + id: Int, + overview: String, + name: String, + voteAverage: Float, + voteCount: Int, + backdropPath: String?, + genreIds: List, + originalLanguage: String, + originalName: String, + posterPath: String?, + popularity: Float, + releaseDate: String, + @SerializedName("adult") val isAdult: Boolean, + @SerializedName("video") val video: Boolean, +): SearchResultMedia( + SearchResultType.MOVIE, id, overview, name, voteAverage, voteCount, releaseDate, + backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultPerson.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultPerson.kt new file mode 100644 index 0000000..7412fb8 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultPerson.kt @@ -0,0 +1,12 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +class SearchResultPerson( + @SerializedName("profile_path") val profilePath: String, + @SerializedName("adult") val isAdult: Boolean, + @SerializedName("id") val id: Int, + @SerializedName("name") val name: String, + @SerializedName("known_for") val knownFor: List, + popularity: Float +): SortableSearchResult(popularity) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultTv.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultTv.kt new file mode 100644 index 0000000..a0bacb4 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SearchResultTv.kt @@ -0,0 +1,22 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +class SearchResultTv( + id: Int, + overview: String, + name: String, + voteAverage: Float, + voteCount: Int, + backdropPath: String?, + genreIds: List, + originalLanguage: String, + originalName: String, + posterPath: String?, + popularity: Float, + releaseDate: String, + @SerializedName("origin_country") val originCountry: List, +): SearchResultMedia( + SearchResultType.TV, id, overview, name, voteAverage, voteCount, releaseDate, + backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SortableSearchResult.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SortableSearchResult.kt new file mode 100644 index 0000000..a50f6ad --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/SortableSearchResult.kt @@ -0,0 +1,7 @@ +package com.owenlejeune.tvtime.api.tmdb.api.v3.model + +import com.google.gson.annotations.SerializedName + +abstract class SortableSearchResult( + @SerializedName("popularity") val popularity: Float +) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt b/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt index 6db9510..1708a84 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/di/modules/modules.kt @@ -3,20 +3,36 @@ package com.owenlejeune.tvtime.di.modules import com.google.gson.JsonDeserializer import com.owenlejeune.tvtime.BuildConfig import com.owenlejeune.tvtime.api.* +import com.owenlejeune.tvtime.api.tmdb.TmdbClient +import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer +import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.utils.ResourceUtils import org.koin.dsl.module -import java.lang.reflect.Type -import kotlin.reflect.KClass val networkModule = module { single { if (BuildConfig.DEBUG) DebugHttpClient() else ProdHttpClient() } - single { GsonConverter() } + single { GsonConverter() } factory { (baseUrl: String) -> Client(baseUrl) } - single, JsonDeserializer<*>>> { mapOf(ListItem::class.java to ListItemDeserializer()) } + single { TmdbClient() } + single { get().createV4AuthenticationService() } + single { get().createAccountService() } + single { get().createGuestSessionService() } + single { get().createAuthenticationService() } + single { get().createMovieService() } + single { get().createPeopleService() } + single { get().createSearchService() } + single { get().createTvService() } + + single, JsonDeserializer<*>>> { + mapOf( + ListItem::class.java to ListItemDeserializer(), + KnownFor::class.java to KnownForDeserializer() + ) + } } val preferencesModule = module {