mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-15 00:02:48 -05:00
add multi search
This commit is contained in:
@@ -413,11 +413,20 @@ class MainActivity : MonetCompatActivity() {
|
||||
)
|
||||
) {
|
||||
it.arguments?.let { arguments ->
|
||||
val title = arguments.getString(NavConstants.SEARCH_TITLE_KEY) ?: ""
|
||||
val type = if (preferences.multiSearch) {
|
||||
MediaViewType.MIXED
|
||||
// val title = arguments.getString(NavConstants.SEARCH_TITLE_KEY) ?: ""
|
||||
// val type = if (preferences.multiSearch) {
|
||||
// MediaViewType.MIXED
|
||||
// } else {
|
||||
// MediaViewType[arguments.getInt(NavConstants.SEARCH_ID_KEY)]
|
||||
// }
|
||||
|
||||
val (type, title) = if (preferences.multiSearch) {
|
||||
Pair(MediaViewType.MIXED, "")
|
||||
} else {
|
||||
MediaViewType[arguments.getInt(NavConstants.SEARCH_ID_KEY)]
|
||||
Pair(
|
||||
MediaViewType[arguments.getInt(NavConstants.SEARCH_ID_KEY)],
|
||||
arguments.getString(NavConstants.SEARCH_TITLE_KEY) ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
SearchScreen(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.owenlejeune.tvtime.api
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonDeserializer
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -9,17 +10,8 @@ import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class GsonConverter: ConverterFactoryFactory, KoinComponent {
|
||||
|
||||
private val deserializers: Map<Class<*>, JsonDeserializer<*>> by inject()
|
||||
private val gson: Gson by inject()
|
||||
|
||||
override fun get(): Converter.Factory {
|
||||
val builder = GsonBuilder()
|
||||
override fun get(): Converter.Factory = GsonConverterFactory.create(gson)
|
||||
|
||||
deserializers.forEach { deserializer ->
|
||||
builder.registerTypeAdapter(deserializer.key, deserializer.value)
|
||||
}
|
||||
|
||||
val gson = builder.create()
|
||||
|
||||
return GsonConverterFactory.create(gson)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api
|
||||
|
||||
import com.google.gson.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.reflect.Type
|
||||
|
||||
abstract class BaseDeserializer<T>: JsonDeserializer<T>, KoinComponent {
|
||||
|
||||
protected val gson: Gson by inject()
|
||||
|
||||
override fun deserialize(
|
||||
json: JsonElement?,
|
||||
typeOfT: Type?,
|
||||
context: JsonDeserializationContext?
|
||||
): T {
|
||||
if (json?.isJsonObject == true) {
|
||||
val obj = json.asJsonObject
|
||||
return processJson(obj)
|
||||
}
|
||||
throw JsonParseException("Not a json object")
|
||||
}
|
||||
|
||||
protected abstract fun processJson(obj: JsonObject): T
|
||||
|
||||
}
|
||||
@@ -27,6 +27,6 @@ interface SearchApi {
|
||||
suspend fun searchPeople(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<SearchResultPerson>>
|
||||
|
||||
@GET("search/multi")
|
||||
suspend fun searchMulti(@Query("query") query: String, @Query("page") page: Int)
|
||||
suspend fun searchMulti(@Query("query") query: String, @Query("page") page: Int): Response<SearchResult<SortableSearchResult>>
|
||||
|
||||
}
|
||||
@@ -39,4 +39,8 @@ class SearchService: KoinComponent {
|
||||
return service.searchPeople(query, page)
|
||||
}
|
||||
|
||||
suspend fun searchMulti(query: String, page: Int = 1): Response<SearchResult<SortableSearchResult>> {
|
||||
return service.searchMulti(query, page)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +1,26 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer
|
||||
|
||||
import com.google.gson.*
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParseException
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.BaseDeserializer
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownForMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownForTv
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class KnownForDeserializer: JsonDeserializer<KnownFor> {
|
||||
class KnownForDeserializer: BaseDeserializer<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(), KnownForMovie::class.java)
|
||||
MediaViewType.TV -> Gson().fromJson(obj.toString(), KnownForTv::class.java)
|
||||
else -> throw JsonParseException("Not a valid MediaViewType: $typeStr")
|
||||
}
|
||||
override fun processJson(obj: JsonObject): KnownFor {
|
||||
if (obj.has(MediaViewType.JSON_KEY)) {
|
||||
val typeStr = obj.get(MediaViewType.JSON_KEY).asString
|
||||
return when (gson.fromJson(typeStr, MediaViewType::class.java)) {
|
||||
MediaViewType.MOVIE -> gson.fromJson(obj.toString(), KnownForMovie::class.java)
|
||||
MediaViewType.TV -> gson.fromJson(obj.toString(), KnownForTv::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")
|
||||
throw JsonParseException("JSON object has no property ${MediaViewType.JSON_KEY}")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParseException
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.BaseDeserializer
|
||||
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.SortableSearchResult
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
|
||||
class SortableSearchResultDeserializer: BaseDeserializer<SortableSearchResult>() {
|
||||
|
||||
override fun processJson(obj: JsonObject): SortableSearchResult {
|
||||
if (obj.has(MediaViewType.JSON_KEY)) {
|
||||
val typeStr = obj.get(MediaViewType.JSON_KEY).asString
|
||||
return when (gson.fromJson(typeStr, MediaViewType::class.java)) {
|
||||
MediaViewType.PERSON -> gson.fromJson(obj.toString(), SearchResultPerson::class.java)
|
||||
MediaViewType.MOVIE -> gson.fromJson(obj.toString(), SearchResultMovie::class.java)
|
||||
MediaViewType.TV -> gson.fromJson(obj.toString(), SearchResultTv::class.java)
|
||||
else -> throw JsonParseException("Not a valid MediaViewType: $typeStr")
|
||||
}
|
||||
}
|
||||
throw JsonParseException("JSON object has no property ${MediaViewType.JSON_KEY}")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import com.google.gson.annotations.SerializedName
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
|
||||
abstract class SearchResultMedia(
|
||||
var type: MediaViewType,
|
||||
@SerializedName("overview") val overview: String,
|
||||
@SerializedName("vote_average") val voteAverage: Float,
|
||||
@SerializedName("vote_count") val voteCount: Int,
|
||||
@@ -14,7 +13,8 @@ abstract class SearchResultMedia(
|
||||
@SerializedName("original_language") val originalLanguage: String,
|
||||
@SerializedName("original_name", alternate = ["original_title"]) val originalName: String,
|
||||
@SerializedName("poster_path") val posterPath: String?,
|
||||
type: MediaViewType,
|
||||
id: Int,
|
||||
name: String,
|
||||
popularity: Float
|
||||
): SortableSearchResult(popularity, id, name)
|
||||
): SortableSearchResult(type, popularity, id, name)
|
||||
@@ -19,6 +19,6 @@ class SearchResultMovie(
|
||||
@SerializedName("adult") val isAdult: Boolean,
|
||||
@SerializedName("video") val video: Boolean,
|
||||
): SearchResultMedia(
|
||||
MediaViewType.MOVIE, overview, voteAverage, voteCount, releaseDate, backdropPath,
|
||||
genreIds, originalLanguage, originalName, posterPath, id, name, popularity
|
||||
overview, voteAverage, voteCount, releaseDate, backdropPath, genreIds,
|
||||
originalLanguage, originalName, posterPath, MediaViewType.MOVIE, id, name, popularity
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
|
||||
class SearchResultPerson(
|
||||
@SerializedName("profile_path") val profilePath: String,
|
||||
@@ -9,4 +10,4 @@ class SearchResultPerson(
|
||||
id: Int,
|
||||
name: String,
|
||||
popularity: Float
|
||||
): SortableSearchResult(popularity, id, name)
|
||||
): SortableSearchResult(MediaViewType.PERSON, popularity, id, name)
|
||||
@@ -18,6 +18,6 @@ class SearchResultTv(
|
||||
releaseDate: String,
|
||||
@SerializedName("origin_country") val originCountry: List<String>,
|
||||
): SearchResultMedia(
|
||||
MediaViewType.TV, overview, voteAverage, voteCount, releaseDate, backdropPath,
|
||||
genreIds, originalLanguage, originalName, posterPath, id, name, popularity
|
||||
overview, voteAverage, voteCount, releaseDate, backdropPath, genreIds,
|
||||
originalLanguage, originalName, posterPath, MediaViewType.TV, id, name, popularity
|
||||
)
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
|
||||
abstract class SortableSearchResult(
|
||||
@SerializedName("media_type") val mediaType: MediaViewType,
|
||||
@SerializedName("popularity") val popularity: Float,
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("name", alternate = ["title"]) val name: String
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer
|
||||
|
||||
import com.google.gson.*
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.BaseDeserializer
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListTv
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ListItemDeserializer: JsonDeserializer<ListItem> {
|
||||
class ListItemDeserializer: BaseDeserializer<ListItem>() {
|
||||
|
||||
companion object {
|
||||
const val MEDIA_TYPE = "media_type"
|
||||
}
|
||||
|
||||
override fun deserialize(
|
||||
json: JsonElement?,
|
||||
typeOfT: Type?,
|
||||
context: JsonDeserializationContext?
|
||||
): ListItem? {
|
||||
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(), ListMovie::class.java)
|
||||
MediaViewType.TV -> Gson().fromJson(obj.toString(), ListTv::class.java)
|
||||
else -> throw JsonParseException("Not a valid MediaViewType: $typeStr")
|
||||
}
|
||||
override fun processJson(obj: JsonObject): ListItem {
|
||||
if (obj.has(MediaViewType.JSON_KEY)) {
|
||||
val typeStr = obj.get(MediaViewType.JSON_KEY).asString
|
||||
return when (gson.fromJson(typeStr, MediaViewType::class.java)) {
|
||||
MediaViewType.MOVIE -> gson.fromJson(obj.toString(), ListMovie::class.java)
|
||||
MediaViewType.TV -> gson.fromJson(obj.toString(), ListTv::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")
|
||||
throw JsonParseException("JSON object has no property ${MediaViewType.JSON_KEY}")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.owenlejeune.tvtime.di.modules
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
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.deserializer.SortableSearchResultDeserializer
|
||||
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.v4.deserializer.ListItemDeserializer
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
@@ -32,9 +36,18 @@ val networkModule = module {
|
||||
single<Map<Class<*>, JsonDeserializer<*>>> {
|
||||
mapOf(
|
||||
ListItem::class.java to ListItemDeserializer(),
|
||||
KnownFor::class.java to KnownForDeserializer()
|
||||
KnownFor::class.java to KnownForDeserializer(),
|
||||
SortableSearchResult::class.java to SortableSearchResultDeserializer()
|
||||
)
|
||||
}
|
||||
|
||||
single {
|
||||
GsonBuilder().apply {
|
||||
get<Map<Class<*>, JsonDeserializer<*>>>().forEach { des ->
|
||||
registerTypeAdapter(des.key, des.value)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
||||
val preferencesModule = module {
|
||||
|
||||
@@ -108,7 +108,6 @@ fun SearchScreen(
|
||||
SearchResultListView(
|
||||
showLoadingAnimation = showLoadingAnimation,
|
||||
currentQuery = searchValue,
|
||||
resultsSortingStrategy = { o1, o2 -> o2.popularity.compareTo(o1.popularity) },
|
||||
searchExecutor = { searchResults: MutableState<List<SearchResultTv>> ->
|
||||
searchTv(searchValue.value, searchResults)
|
||||
}
|
||||
@@ -120,7 +119,6 @@ fun SearchScreen(
|
||||
SearchResultListView(
|
||||
showLoadingAnimation = showLoadingAnimation,
|
||||
currentQuery = searchValue,
|
||||
resultsSortingStrategy = { o1, o2 -> o2.popularity.compareTo(o1.popularity) },
|
||||
searchExecutor = { searchResults: MutableState<List<SearchResultMovie>> ->
|
||||
searchMovies(searchValue.value, searchResults)
|
||||
}
|
||||
@@ -132,7 +130,6 @@ fun SearchScreen(
|
||||
SearchResultListView(
|
||||
showLoadingAnimation = showLoadingAnimation,
|
||||
currentQuery = searchValue,
|
||||
resultsSortingStrategy = { o1, o2 -> o2.popularity.compareTo(o1.popularity) },
|
||||
searchExecutor = { searchResults: MutableState<List<SearchResultPerson>> ->
|
||||
searchPeople(searchValue.value, searchResults)
|
||||
}
|
||||
@@ -145,11 +142,24 @@ fun SearchScreen(
|
||||
showLoadingAnimation = showLoadingAnimation,
|
||||
currentQuery = searchValue,
|
||||
searchExecutor = { searchResults: MutableState<List<SortableSearchResult>> ->
|
||||
|
||||
searchMulti(searchValue.value, searchResults)
|
||||
},
|
||||
resultsSortingStrategy = { o1, o2 -> o2.popularity.compareTo(o1.popularity) }
|
||||
) { 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 -> {}
|
||||
@@ -167,7 +177,6 @@ private fun <T: SortableSearchResult> SearchResultListView(
|
||||
showLoadingAnimation: MutableState<Boolean>,
|
||||
currentQuery: MutableState<String>,
|
||||
searchExecutor: (MutableState<List<T>>) -> Unit,
|
||||
resultsSortingStrategy: Comparator<T>? = null,
|
||||
viewRenderer: @Composable (T) -> Unit
|
||||
) {
|
||||
val searchResults = remember { mutableStateOf(emptyList<T>()) }
|
||||
@@ -184,7 +193,7 @@ private fun <T: SortableSearchResult> SearchResultListView(
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = "No search results found",
|
||||
text = stringResource(R.string.no_search_results),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
fontSize = 18.sp
|
||||
@@ -196,9 +205,7 @@ private fun <T: SortableSearchResult> SearchResultListView(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
val items = resultsSortingStrategy?.let {
|
||||
searchResults.value.sortedWith(resultsSortingStrategy)
|
||||
} ?: searchResults.value
|
||||
val items = searchResults.value.sortedByDescending { it.popularity }
|
||||
listItems(items) { item ->
|
||||
viewRenderer(item)
|
||||
}
|
||||
@@ -391,6 +398,20 @@ private fun searchPeople(
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -13,6 +13,8 @@ enum class MediaViewType {
|
||||
MIXED;
|
||||
|
||||
companion object {
|
||||
const val JSON_KEY = "media_type"
|
||||
|
||||
operator fun get(oridinal: Int): MediaViewType {
|
||||
return values()[oridinal]
|
||||
}
|
||||
|
||||
@@ -123,4 +123,5 @@
|
||||
|
||||
<!-- search results -->
|
||||
<string name="search_result_tv_services">TV Series</string>
|
||||
<string name="no_search_results">No search results found</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user