From 382f9f287758c18d0a250b70e82b143f8177b542 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 21:46:04 +0200 Subject: [PATCH 1/2] implementing discover --- .../cloudstream3/ui/APIRepository.kt | 9 +++ .../cloudstream3/ui/home/HomeViewModel.kt | 1 + .../cloudstream3/ui/search/SearchFragment.kt | 31 ++++++++++ .../cloudstream3/ui/search/SearchViewModel.kt | 60 +++++++++++++++++++ .../ui/settings/SettingsPlayer.kt | 2 + app/src/main/res/layout/fragment_search.xml | 8 +++ 6 files changed, 111 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 492efaceca..de2178211d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -4,6 +4,7 @@ import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.Genres import com.lagradost.cloudstream3.HomePageResponse import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainAPI @@ -142,6 +143,14 @@ class APIRepository(val api: MainAPI) { } } + suspend fun discover(types: List, genres: List): Resource> { + return safeApiCall { + withTimeout(getTimeout(api.searchTimeoutMs)) { + api.discover(types, genres) ?: throw ErrorLoadingException() + } + } + } + suspend fun waitForHomeDelay() { val delta = api.sequentialMainPageScrollDelay + api.lastHomepageRequest - unixTimeMS if (delta < 0) return diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 60f2c18015..0d38949f3d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -149,6 +149,7 @@ class HomeViewModel : ViewModel() { } } + fun loadStoredData(preferredWatchStatus: Set?) = viewModelScope.launchSafe { val watchStatusIds = withContext(Dispatchers.IO) { getAllWatchStateIds()?.map { id -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 1922e4fae9..f0f01b0a1d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AnimeSearchResponse import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainActivity @@ -42,12 +43,15 @@ import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.FragmentSearchBinding import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.ui.APIRepository.Companion.getTimeout import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips @@ -70,11 +74,14 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard +import kotlinx.coroutines.withTimeout import java.util.Locale import java.util.concurrent.locks.ReentrantLock @@ -273,6 +280,30 @@ class SearchFragment : Fragment() { } } + binding?.searchGenreFilter?.setOnClickListener { + val name = DataStoreHelper.currentHomePage + val provider = getApiFromNameNull(name) + val availableGenres = provider?.supportedGenres + val prefNames = availableGenres?.map { + it.name + } ?: return@setOnClickListener + activity?.showMultiDialog( + prefNames, + listOf(), + "Genres", + {}, + { enabledIndices -> + val listOfSelectedGenres = if (enabledIndices.isEmpty()) { + availableGenres + } else { + availableGenres.filterIndexed { index, _ -> index in enabledIndices } + } + searchViewModel.discoverAndCancel(provider.supportedTypes.toList(), listOfSelectedGenres.toList(), provider) + } + ) + } + + val searchExitIcon = binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) // val searchMagIcon = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index 839b9d3f83..a0736b620f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -5,10 +5,14 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.Genres +import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe @@ -63,6 +67,15 @@ class SearchViewModel : ViewModel() { onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch) } + fun discoverAndCancel( + types: List, + genres: List, + provider: MainAPI, + ) { + onGoingSearch?.cancel() + onGoingSearch = discover(types, genres, provider) + } + fun updateHistory() = viewModelScope.launch { ioSafe { val items = getKeys("$currentAccount/$SEARCH_HISTORY_KEY")?.mapNotNull { @@ -72,6 +85,8 @@ class SearchViewModel : ViewModel() { } } + + private fun search( query: String, providersActive: Set, @@ -141,4 +156,49 @@ class SearchViewModel : ViewModel() { _searchResponse.postValue(Resource.Success(list)) } } + + + private fun discover( + types: List, + genres: List, + provider: MainAPI, + ) = + viewModelScope.launchSafe { + + _searchResponse.postValue(Resource.Loading()) + val currentIndex = currentSearchIndex + + _currentSearch.postValue(ArrayList()) + + withContext(Dispatchers.IO) { // This interrupts UI otherwise + val currentList = ArrayList() + val a = APIRepository(provider) + val discover = a?.discover(types, genres) ?: return@withContext + + currentList.add(OnGoingSearch(provider.name, discover)) + if (currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug TODO useless ?? + + _currentSearch.postValue(currentList) + val list = ArrayList() + val nestedList = + currentList.map { it.data } + .filterIsInstance>>().map { it.value } + + // I do it this way to move the relevant search results to the top TODO remove ?? + var index = 0 + while (true) { + var added = 0 + for (sublist in nestedList) { + if (sublist.size > index) { + list.add(sublist[index]) + added++ + } + } + if (added == 0) break + index++ + } + + _searchResponse.postValue(Resource.Success(list)) + } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index 5c6acdd9b8..b2964f3a69 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -8,6 +8,7 @@ import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -176,6 +177,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { } getPref(R.string.player_default_key)?.setOnPreferenceClickListener { + //val api = HomeViewModel val players = VideoClickActionHolder.getPlayers(activity) val prefNames = buildList { add(getString(R.string.player_settings_play_in_app)) diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index aeffc67395..a26cad2875 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -110,6 +110,14 @@ + + From 0d54106aca5aab76a53f32fe403e25026c575819 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 21:55:47 +0200 Subject: [PATCH 2/2] fix --- .../com/lagradost/cloudstream3/MainAPI.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 7da8f421bf..d43bbe8fdb 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -484,6 +484,7 @@ abstract class MainAPI { open val hasMainPage = false open val hasQuickSearch = false + open val hasDiscover = false /** * The timeout on the `loadLinks` functions in milliseconds, @@ -545,6 +546,12 @@ abstract class MainAPI { TvType.OVA, ) + open val supportedGenres = setOf( + Genres.Action, + Genres.Horror, + Genres.Romance + ) + open val vpnStatus = VPNStatus.None open val providerType = ProviderType.DirectProvider @@ -564,6 +571,10 @@ abstract class MainAPI { throw NotImplementedError() } + open suspend fun discover(types: List, genres: List): List? { + throw NotImplementedError() + } + // @WorkerThread open suspend fun quickSearch(query: String): List? { throw NotImplementedError() @@ -969,6 +980,14 @@ class Score private constructor( } } +@Suppress("UNUSED_PARAMETER") +enum class Genres(value: Int?) { + Action(1), + Romance(2), + Horror(3) +} + + @Suppress("UNUSED_PARAMETER") enum class TvType(value: Int?) { Movie(1),