begin search

This commit is contained in:
Owen LeJeune
2022-03-28 12:29:02 -04:00
parent eea18fa4a5
commit cd8af13180
18 changed files with 395 additions and 13 deletions

View File

@@ -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()

View File

@@ -2,7 +2,7 @@ package com.owenlejeune.tvtime.api
import retrofit2.Converter
interface Converter {
interface ConverterFactoryFactory {
fun get(): Converter.Factory

View File

@@ -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<Class<*>, 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 ->

View File

@@ -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)

View File

@@ -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<SearchResult<ProductionCompany>>
@GET("search/collection")
suspend fun searchCollections(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<Collection>>
@GET("search/keyword")
suspend fun searchKeywords(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<Keyword>>
@GET("search/movie")
suspend fun searchMovies(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<SearchResultMovie>>
@GET("search/tv")
suspend fun searchTv(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<SearchResultTv>>
@GET("search/person")
suspend fun searchPeople(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<SearchResultPerson>>
}

View File

@@ -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<SearchResult<ProductionCompany>> {
return service.searchCompanies(query, page)
}
suspend fun searchCollections(query: String, page: Int = 1): Response<SearchResult<Collection>> {
return service.searchCollections(query, page)
}
suspend fun searchKeywords(query: String, page: Int = 1): Response<SearchResult<Keyword>> {
return service.searchKeywords(query, page)
}
suspend fun searchMovies(query: String, page: Int = 1): Response<SearchResult<SearchResultMovie>> {
return service.searchMovies(query, page)
}
suspend fun searchTv(query: String, page: Int = 1): Response<SearchResult<SearchResultTv>> {
return service.searchTv(query, page)
}
suspend fun searchPeople(query: String, page: Int = 1): Response<SearchResult<SearchResultPerson>> {
return service.searchPeople(query, page)
}
suspend fun searchAll(query: String, onResultsUpdated: (List<Any>) -> Unit) {
val results = TreeSet<Any> {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)
}
}
}
}
}

View File

@@ -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<KnownFor> {
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")
}
}

View File

@@ -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<Int>,
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<String>
) : KnownFor(
backdropPath,
releaseDate,
genreIds,
id,
mediaType,
title,
originalLanguage,
originalTitle,
overview,
popularity,
posterPath,
voteAverage,
voteCount
)

View File

@@ -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<Int>,
@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
)

View File

@@ -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<Int>,
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
)

View File

@@ -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?
)

View File

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

View File

@@ -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<Int>,
@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
}
}

View File

@@ -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<Int>,
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
)

View File

@@ -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<KnownFor>,
popularity: Float
): SortableSearchResult(popularity)

View File

@@ -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<Int>,
originalLanguage: String,
originalName: String,
posterPath: String?,
popularity: Float,
releaseDate: String,
@SerializedName("origin_country") val originCountry: List<String>,
): SearchResultMedia(
SearchResultType.TV, id, overview, name, voteAverage, voteCount, releaseDate,
backdropPath, genreIds, originalLanguage, originalName, posterPath, popularity
)

View File

@@ -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
)

View File

@@ -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<Converter> { GsonConverter() }
single<ConverterFactoryFactory> { GsonConverter() }
factory { (baseUrl: String) -> Client(baseUrl) }
single<Map<Class<*>, JsonDeserializer<*>>> { mapOf(ListItem::class.java to ListItemDeserializer()) }
single { TmdbClient() }
single { get<TmdbClient>().createV4AuthenticationService() }
single { get<TmdbClient>().createAccountService() }
single { get<TmdbClient>().createGuestSessionService() }
single { get<TmdbClient>().createAuthenticationService() }
single { get<TmdbClient>().createMovieService() }
single { get<TmdbClient>().createPeopleService() }
single { get<TmdbClient>().createSearchService() }
single { get<TmdbClient>().createTvService() }
single<Map<Class<*>, JsonDeserializer<*>>> {
mapOf(
ListItem::class.java to ListItemDeserializer(),
KnownFor::class.java to KnownForDeserializer()
)
}
}
val preferencesModule = module {