forked from recloudstream/cloudstream
anilist/mal search
This commit is contained in:
parent
a8b6fc4e70
commit
28fcf68b14
10 changed files with 396 additions and 45 deletions
|
@ -3,12 +3,12 @@ package com.lagradost.cloudstream3.syncproviders
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.lagradost.cloudstream3.ShowStatus
|
import com.lagradost.cloudstream3.ShowStatus
|
||||||
|
|
||||||
interface SyncAPI {
|
interface SyncAPI : OAuth2API {
|
||||||
data class SyncSearchResult(
|
data class SyncSearchResult(
|
||||||
val name: String,
|
val name: String,
|
||||||
val syncApiName: String,
|
val syncApiName: String,
|
||||||
val id: String,
|
val id: String,
|
||||||
val url: String?,
|
val url: String,
|
||||||
val posterUrl: String?,
|
val posterUrl: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ interface SyncAPI {
|
||||||
var characters: List<SyncCharacter>? = null,
|
var characters: List<SyncCharacter>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val icon : Int
|
val icon: Int
|
||||||
|
|
||||||
val mainUrl: String
|
val mainUrl: String
|
||||||
fun search(context: Context, name: String): List<SyncSearchResult>?
|
fun search(context: Context, name: String): List<SyncSearchResult>?
|
||||||
|
@ -78,9 +78,9 @@ interface SyncAPI {
|
||||||
4 -> PlanToWatch
|
4 -> PlanToWatch
|
||||||
5 -> ReWatching
|
5 -> ReWatching
|
||||||
*/
|
*/
|
||||||
fun score(context: Context, id: String, status : SyncStatus): Boolean
|
fun score(context: Context, id: String, status: SyncStatus): Boolean
|
||||||
|
|
||||||
fun getStatus(context: Context, id : String) : SyncStatus?
|
fun getStatus(context: Context, id: String): SyncStatus?
|
||||||
|
|
||||||
fun getResult(context: Context, id: String): SyncResult?
|
fun getResult(context: Context, id: String): SyncResult?
|
||||||
}
|
}
|
|
@ -62,7 +62,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
|
|
||||||
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult> {
|
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult> {
|
||||||
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
||||||
val res = get(
|
var res = get(
|
||||||
url, headers = mapOf(
|
url, headers = mapOf(
|
||||||
"Authorization" to "Bearer " + context.getKey<String>(
|
"Authorization" to "Bearer " + context.getKey<String>(
|
||||||
accountId,
|
accountId,
|
||||||
|
@ -71,12 +71,13 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
), cacheTime = 0
|
), cacheTime = 0
|
||||||
).text
|
).text
|
||||||
return mapper.readValue<MalSearch>(res).data.map {
|
return mapper.readValue<MalSearch>(res).data.map {
|
||||||
|
val node = it.node
|
||||||
SyncAPI.SyncSearchResult(
|
SyncAPI.SyncSearchResult(
|
||||||
it.title,
|
node.title,
|
||||||
this.name,
|
this.name,
|
||||||
it.id.toString(),
|
node.id.toString(),
|
||||||
"$mainUrl/anime/${it.id}/",
|
"$mainUrl/anime/${node.id}/",
|
||||||
it.main_picture?.large ?: it.main_picture?.medium
|
node.main_picture?.large ?: node.main_picture?.medium
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,26 +226,26 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
@JsonProperty("id") val id: Int,
|
@JsonProperty("id") val id: Int,
|
||||||
@JsonProperty("title") val title: String,
|
@JsonProperty("title") val title: String,
|
||||||
@JsonProperty("main_picture") val main_picture: MainPicture?,
|
@JsonProperty("main_picture") val main_picture: MainPicture?,
|
||||||
@JsonProperty("alternative_titles") val alternative_titles: AlternativeTitles,
|
@JsonProperty("alternative_titles") val alternative_titles: AlternativeTitles?,
|
||||||
@JsonProperty("media_type") val media_type: String,
|
@JsonProperty("media_type") val media_type: String?,
|
||||||
@JsonProperty("num_episodes") val num_episodes: Int,
|
@JsonProperty("num_episodes") val num_episodes: Int?,
|
||||||
@JsonProperty("status") val status: String,
|
@JsonProperty("status") val status: String?,
|
||||||
@JsonProperty("start_date") val start_date: String?,
|
@JsonProperty("start_date") val start_date: String?,
|
||||||
@JsonProperty("end_date") val end_date: String?,
|
@JsonProperty("end_date") val end_date: String?,
|
||||||
@JsonProperty("average_episode_duration") val average_episode_duration: Int,
|
@JsonProperty("average_episode_duration") val average_episode_duration: Int?,
|
||||||
@JsonProperty("synopsis") val synopsis: String,
|
@JsonProperty("synopsis") val synopsis: String?,
|
||||||
@JsonProperty("mean") val mean: Double,
|
@JsonProperty("mean") val mean: Double?,
|
||||||
@JsonProperty("genres") val genres: List<Genres>?,
|
@JsonProperty("genres") val genres: List<Genres>?,
|
||||||
@JsonProperty("rank") val rank: Int,
|
@JsonProperty("rank") val rank: Int?,
|
||||||
@JsonProperty("popularity") val popularity: Int,
|
@JsonProperty("popularity") val popularity: Int?,
|
||||||
@JsonProperty("num_list_users") val num_list_users: Int,
|
@JsonProperty("num_list_users") val num_list_users: Int?,
|
||||||
@JsonProperty("num_favorites") val num_favorites: Int,
|
@JsonProperty("num_favorites") val num_favorites: Int?,
|
||||||
@JsonProperty("num_scoring_users") val num_scoring_users: Int,
|
@JsonProperty("num_scoring_users") val num_scoring_users: Int?,
|
||||||
@JsonProperty("start_season") val start_season: StartSeason?,
|
@JsonProperty("start_season") val start_season: StartSeason?,
|
||||||
@JsonProperty("broadcast") val broadcast: Broadcast?,
|
@JsonProperty("broadcast") val broadcast: Broadcast?,
|
||||||
@JsonProperty("nsfw") val nsfw: String,
|
@JsonProperty("nsfw") val nsfw: String?,
|
||||||
@JsonProperty("created_at") val created_at: String,
|
@JsonProperty("created_at") val created_at: String?,
|
||||||
@JsonProperty("updated_at") val updated_at: String
|
@JsonProperty("updated_at") val updated_at: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ListStatus(
|
data class ListStatus(
|
||||||
|
@ -600,14 +601,18 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
// Used for getDataAboutId()
|
// Used for getDataAboutId()
|
||||||
data class MalAnime(
|
data class MalAnime(
|
||||||
@JsonProperty("id") val id: Int,
|
@JsonProperty("id") val id: Int,
|
||||||
@JsonProperty("title") val title: String,
|
@JsonProperty("title") val title: String?,
|
||||||
@JsonProperty("num_episodes") val num_episodes: Int,
|
@JsonProperty("num_episodes") val num_episodes: Int,
|
||||||
@JsonProperty("my_list_status") val my_list_status: MalStatus?,
|
@JsonProperty("my_list_status") val my_list_status: MalStatus?,
|
||||||
@JsonProperty("main_picture") val main_picture: MalMainPicture?,
|
@JsonProperty("main_picture") val main_picture: MalMainPicture?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class MalSearchNode(
|
||||||
|
@JsonProperty("node") val node: Node,
|
||||||
|
)
|
||||||
|
|
||||||
data class MalSearch(
|
data class MalSearch(
|
||||||
@JsonProperty("data") val data: List<MalAnime>,
|
@JsonProperty("data") val data: List<MalSearchNode>,
|
||||||
//paging
|
//paging
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.quicksearch
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList
|
||||||
|
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchViewModel
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
|
import kotlinx.android.synthetic.main.fragment_search.*
|
||||||
|
import kotlinx.android.synthetic.main.quick_search.*
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
|
class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
|
||||||
|
companion object {
|
||||||
|
fun push(activity: Activity?, mainApi: Boolean = true, autoSearch: String? = null) {
|
||||||
|
activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply {
|
||||||
|
putBoolean("mainapi", mainApi)
|
||||||
|
putString("autosearch", autoSearch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val searchViewModel: SearchViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
activity?.window?.setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||||
|
)
|
||||||
|
|
||||||
|
return inflater.inflate(R.layout.quick_search, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
context?.fixPaddingStatusbar(quick_search_root)
|
||||||
|
|
||||||
|
arguments?.getBoolean("mainapi", true)?.let {
|
||||||
|
isMainApis = it
|
||||||
|
}
|
||||||
|
|
||||||
|
val listLock = ReentrantLock()
|
||||||
|
observe(searchViewModel.currentSearch) { list ->
|
||||||
|
try {
|
||||||
|
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
|
||||||
|
listLock.lock()
|
||||||
|
(quick_search_master_recycler?.adapter as ParentItemAdapter?)?.apply {
|
||||||
|
items = list.map { ongoing ->
|
||||||
|
val ongoingList = HomePageList(
|
||||||
|
ongoing.apiName,
|
||||||
|
if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList()
|
||||||
|
)
|
||||||
|
ongoingList
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
} finally {
|
||||||
|
listLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf(), { callback ->
|
||||||
|
when (callback.action) {
|
||||||
|
SEARCH_ACTION_LOAD -> {
|
||||||
|
if (isMainApis) {
|
||||||
|
// this is due to result page only holding 1 thing
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
|
||||||
|
SearchHelper.handleSearchClickCallback(activity, callback)
|
||||||
|
} else {
|
||||||
|
//TODO MAL RESPONSE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> SearchHelper.handleSearchClickCallback(activity, callback)
|
||||||
|
}
|
||||||
|
}, { item ->
|
||||||
|
activity?.loadHomepageList(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
val searchExitIcon = quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||||
|
val searchMagIcon = quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
|
||||||
|
|
||||||
|
searchMagIcon.scaleX = 0.65f
|
||||||
|
searchMagIcon.scaleY = 0.65f
|
||||||
|
quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
context?.let { ctx ->
|
||||||
|
searchViewModel.searchAndCancel(query = query, context = ctx, isMainApis = isMainApis, ignoreSettings = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_search?.let {
|
||||||
|
UIHelper.hideKeyboard(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String): Boolean {
|
||||||
|
//searchViewModel.quickSearch(newText)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
quick_search_loading_bar.alpha = 0f
|
||||||
|
observe(searchViewModel.searchResponse) {
|
||||||
|
when (it) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
it.value.let { data ->
|
||||||
|
if (data.isNotEmpty()) {
|
||||||
|
(cardSpace?.adapter as SearchAdapter?)?.apply {
|
||||||
|
cardList = data.toList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchExitIcon.alpha = 1f
|
||||||
|
quick_search_loading_bar.alpha = 0f
|
||||||
|
}
|
||||||
|
is Resource.Failure -> {
|
||||||
|
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
|
||||||
|
searchExitIcon.alpha = 1f
|
||||||
|
quick_search_loading_bar.alpha = 0f
|
||||||
|
}
|
||||||
|
is Resource.Loading -> {
|
||||||
|
searchExitIcon.alpha = 0f
|
||||||
|
quick_search_loading_bar.alpha = 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_search_master_recycler.adapter = masterAdapter
|
||||||
|
quick_search_master_recycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
|
quick_search.setOnQueryTextFocusChangeListener { _, b ->
|
||||||
|
if (b) {
|
||||||
|
// https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview
|
||||||
|
UIHelper.showInputMethod(view.findFocus())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_search_back.setOnClickListener {
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments?.getString("autosearch")?.let {
|
||||||
|
quick_search.setQuery(it, true)
|
||||||
|
arguments?.remove("autosearch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownload
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerData
|
import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||||
|
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
||||||
|
@ -944,6 +945,10 @@ class ResultFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result_search?.setOnClickListener {
|
||||||
|
QuickSearchFragment.push(activity,true, d.name)
|
||||||
|
}
|
||||||
|
|
||||||
result_share?.setOnClickListener {
|
result_share?.setOnClickListener {
|
||||||
val i = Intent(ACTION_SEND)
|
val i = Intent(ACTION_SEND)
|
||||||
i.type = "text/plain"
|
i.type = "text/plain"
|
||||||
|
|
|
@ -10,7 +10,7 @@ import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -53,15 +53,13 @@ class SearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var searchViewModel: SearchViewModel
|
private val searchViewModel: SearchViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View? {
|
||||||
searchViewModel =
|
|
||||||
ViewModelProvider(this).get(SearchViewModel::class.java)
|
|
||||||
activity?.window?.setSoftInputMode(
|
activity?.window?.setSoftInputMode(
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||||
)
|
)
|
||||||
|
@ -299,7 +297,10 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
searchViewModel.searchAndCancel(query)
|
context?.let { ctx ->
|
||||||
|
searchViewModel.searchAndCancel(query = query, context = ctx)
|
||||||
|
}
|
||||||
|
|
||||||
main_search?.let {
|
main_search?.let {
|
||||||
hideKeyboard(it)
|
hideKeyboard(it)
|
||||||
}
|
}
|
||||||
|
@ -366,7 +367,7 @@ class SearchFragment : Fragment() {
|
||||||
typesActive = it.getApiTypeSettings()
|
typesActive = it.getApiTypeSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
main_search.setOnQueryTextFocusChangeListener { searchView, b ->
|
main_search.setOnQueryTextFocusChangeListener { _, b ->
|
||||||
if (b) {
|
if (b) {
|
||||||
// https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview
|
// https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview
|
||||||
showInputMethod(view.findFocus())
|
showInputMethod(view.findFocus())
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package com.lagradost.cloudstream3.ui.search
|
package com.lagradost.cloudstream3.ui.search
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.lagradost.cloudstream3.APIHolder.apis
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
|
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.apmap
|
import com.lagradost.cloudstream3.apmap
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.providersActive
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -28,18 +34,39 @@ class SearchViewModel : ViewModel() {
|
||||||
val currentSearch: LiveData<ArrayList<OnGoingSearch>> get() = _currentSearch
|
val currentSearch: LiveData<ArrayList<OnGoingSearch>> get() = _currentSearch
|
||||||
|
|
||||||
private val repos = apis.map { APIRepository(it) }
|
private val repos = apis.map { APIRepository(it) }
|
||||||
|
private val syncApis = SyncApis
|
||||||
|
|
||||||
private fun clearSearch() {
|
private fun clearSearch() {
|
||||||
_searchResponse.postValue(Resource.Success(ArrayList()))
|
_searchResponse.postValue(Resource.Success(ArrayList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
var onGoingSearch: Job? = null
|
var onGoingSearch: Job? = null
|
||||||
fun searchAndCancel(query: String) {
|
fun searchAndCancel(query: String, isMainApis : Boolean = true, ignoreSettings : Boolean = false, context: Context) {
|
||||||
onGoingSearch?.cancel()
|
onGoingSearch?.cancel()
|
||||||
onGoingSearch = search(query)
|
onGoingSearch = search(query, isMainApis, ignoreSettings, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun search(query: String) = viewModelScope.launch {
|
data class SyncSearchResultSearchResponse(
|
||||||
|
override val name: String,
|
||||||
|
override val url: String,
|
||||||
|
override val apiName: String,
|
||||||
|
override val type: TvType?,
|
||||||
|
override val posterUrl: String?,
|
||||||
|
override val id: Int?,
|
||||||
|
) : SearchResponse
|
||||||
|
|
||||||
|
private fun SyncAPI.SyncSearchResult.toSearchResponse(): SyncSearchResultSearchResponse {
|
||||||
|
return SyncSearchResultSearchResponse(
|
||||||
|
this.name,
|
||||||
|
this.url,
|
||||||
|
this.syncApiName,
|
||||||
|
null,
|
||||||
|
this.posterUrl,
|
||||||
|
null, //this.id.hashCode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun search(query: String, isMainApis : Boolean = true, ignoreSettings : Boolean = false, context: Context) = viewModelScope.launch {
|
||||||
if (query.length <= 1) {
|
if (query.length <= 1) {
|
||||||
clearSearch()
|
clearSearch()
|
||||||
return@launch
|
return@launch
|
||||||
|
@ -52,17 +79,26 @@ class SearchViewModel : ViewModel() {
|
||||||
_currentSearch.postValue(ArrayList())
|
_currentSearch.postValue(ArrayList())
|
||||||
|
|
||||||
withContext(Dispatchers.IO) { // This interrupts UI otherwise
|
withContext(Dispatchers.IO) { // This interrupts UI otherwise
|
||||||
repos.filter { a ->
|
if (isMainApis) {
|
||||||
(providersActive.size == 0 || providersActive.contains(a.name))
|
repos.filter { a ->
|
||||||
}.apmap { a -> // Parallel
|
ignoreSettings || (providersActive.size == 0 || providersActive.contains(a.name))
|
||||||
val search = a.search(query)
|
}.apmap { a -> // Parallel
|
||||||
currentList.add(OnGoingSearch(a.name,search ))
|
val search = a.search(query)
|
||||||
_currentSearch.postValue(currentList)
|
currentList.add(OnGoingSearch(a.name, search))
|
||||||
|
_currentSearch.postValue(currentList)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncApis.apmap { a ->
|
||||||
|
val search = safeApiCall {
|
||||||
|
a.search(context, query)?.map { it.toSearchResponse() } ?: throw ErrorLoadingException()
|
||||||
|
}
|
||||||
|
|
||||||
|
currentList.add(OnGoingSearch(a.name, search))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentSearch.postValue(currentList)
|
_currentSearch.postValue(currentList)
|
||||||
|
|
||||||
|
|
||||||
val list = ArrayList<SearchResponse>()
|
val list = ArrayList<SearchResponse>()
|
||||||
val nestedList =
|
val nestedList =
|
||||||
currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
|
currentList.map { it.data }.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
|
||||||
|
|
|
@ -232,7 +232,7 @@
|
||||||
android:nextFocusUp="@id/result_back"
|
android:nextFocusUp="@id/result_back"
|
||||||
android:nextFocusDown="@id/result_descript"
|
android:nextFocusDown="@id/result_descript"
|
||||||
android:nextFocusLeft="@id/result_share"
|
android:nextFocusLeft="@id/result_share"
|
||||||
android:nextFocusRight="@id/result_bookmark_button"
|
android:nextFocusRight="@id/result_search"
|
||||||
|
|
||||||
android:id="@+id/result_openinbrower"
|
android:id="@+id/result_openinbrower"
|
||||||
android:layout_width="25dp"
|
android:layout_width="25dp"
|
||||||
|
@ -247,6 +247,26 @@
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:contentDescription="@string/result_open_in_browser">
|
android:contentDescription="@string/result_open_in_browser">
|
||||||
</ImageView>
|
</ImageView>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:nextFocusUp="@id/result_back"
|
||||||
|
android:nextFocusDown="@id/result_descript"
|
||||||
|
android:nextFocusLeft="@id/result_openinbrower"
|
||||||
|
android:nextFocusRight="@id/result_bookmark_button"
|
||||||
|
|
||||||
|
android:id="@+id/result_search"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:elevation="10dp"
|
||||||
|
|
||||||
|
android:tint="?attr/textColor"
|
||||||
|
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/search_icon"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/result_open_in_browser">
|
||||||
|
</ImageView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="10dp"
|
||||||
android:background="@drawable/search_background"
|
android:background="@drawable/search_background"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="45dp"
|
||||||
>
|
>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
|
|
81
app/src/main/res/layout/quick_search.xml
Normal file
81
app/src/main/res/layout/quick_search.xml
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/quick_search_root"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:visibility="visible"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/quick_search_back"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:foregroundGravity="center"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||||
|
app:tint="@android:color/white"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<requestFocus/>
|
||||||
|
</ImageView>
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:background="@drawable/search_background"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="45dp">
|
||||||
|
<androidx.appcompat.widget.SearchView
|
||||||
|
android:nextFocusRight="@id/search_filter"
|
||||||
|
android:nextFocusLeft="@id/search_filter"
|
||||||
|
android:nextFocusDown="@id/cardSpace"
|
||||||
|
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:inputType="text"
|
||||||
|
|
||||||
|
android:id="@+id/quick_search"
|
||||||
|
app:queryBackground="@color/transparent"
|
||||||
|
|
||||||
|
app:searchIcon="@drawable/search_icon"
|
||||||
|
android:paddingStart="-10dp"
|
||||||
|
android:iconifiedByDefault="false"
|
||||||
|
app:queryHint="@string/search_hint"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
app:iconifiedByDefault="false"
|
||||||
|
tools:ignore="RtlSymmetry">
|
||||||
|
|
||||||
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
android:id="@+id/quick_search_loading_bar"
|
||||||
|
android:layout_width="20dp" android:layout_height="20dp"
|
||||||
|
android:layout_marginStart="-35dp"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar"
|
||||||
|
android:foregroundTint="@color/white"
|
||||||
|
android:progressTint="@color/white"
|
||||||
|
android:layout_gravity="center">
|
||||||
|
|
||||||
|
</androidx.core.widget.ContentLoadingProgressBar>
|
||||||
|
<!--app:queryHint="@string/search_hint"
|
||||||
|
android:background="@color/grayBackground" @color/itemBackground
|
||||||
|
app:searchHintIcon="@drawable/search_white"
|
||||||
|
-->
|
||||||
|
</androidx.appcompat.widget.SearchView>
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:descendantFocusability="afterDescendants"
|
||||||
|
|
||||||
|
android:background="?attr/primaryBlackBackground"
|
||||||
|
android:id="@+id/quick_search_master_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/homepage_parent"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
|
@ -81,6 +81,25 @@
|
||||||
/>
|
/>
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<action android:id="@+id/global_to_navigation_quick_search"
|
||||||
|
app:destination="@id/navigation_quick_search"
|
||||||
|
app:enterAnim="@anim/enter_anim"
|
||||||
|
app:exitAnim="@anim/exit_anim"
|
||||||
|
app:popEnterAnim="@anim/enter_anim"
|
||||||
|
app:popExitAnim="@anim/exit_anim">
|
||||||
|
<argument
|
||||||
|
android:name="mainapi"
|
||||||
|
app:argType="boolean"
|
||||||
|
android:defaultValue="true"
|
||||||
|
|
||||||
|
/>
|
||||||
|
<argument
|
||||||
|
android:name="autosearch"
|
||||||
|
app:argType="string"
|
||||||
|
android:defaultValue="@null"
|
||||||
|
/>
|
||||||
|
</action>
|
||||||
|
|
||||||
<action android:id="@+id/global_to_navigation_settings"
|
<action android:id="@+id/global_to_navigation_settings"
|
||||||
app:destination="@id/navigation_settings"
|
app:destination="@id/navigation_settings"
|
||||||
app:enterAnim="@anim/enter_anim"
|
app:enterAnim="@anim/enter_anim"
|
||||||
|
@ -149,6 +168,12 @@
|
||||||
android:name="com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment"
|
android:name="com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment"
|
||||||
android:label="@string/subtitles_settings"/>
|
android:label="@string/subtitles_settings"/>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/navigation_quick_search"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:name="com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment"
|
||||||
|
android:label="@string/search"/>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/navigation_download_child"
|
android:id="@+id/navigation_download_child"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
Loading…
Reference in a new issue