diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index f703e8ad..677bf123 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -10,10 +10,10 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.lagradost.cloudstream3.metaproviders.SyncIdName import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi +import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.* diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt index 41cf413c..75e96bec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt @@ -3,27 +3,24 @@ package com.lagradost.cloudstream3.metaproviders import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi -import com.lagradost.cloudstream3.utils.SyncUtil - -enum class SyncIdName { - AniList, - MyAnimeList, - Trakt, - Imdb -} +import com.lagradost.cloudstream3.syncproviders.SyncIdName object SyncRedirector { val syncApis = SyncApis private val syncIds = listOf( SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""), - SyncIdName.AniList to Regex("""anilist\.co\/anime\/(\d+)""") + SyncIdName.Anilist to Regex("""anilist\.co\/anime\/(\d+)""") ) - suspend fun redirect(url: String, providerApi: MainAPI): String { + suspend fun redirect( + url: String, + providerApi: MainAPI + ): String { + // Deprecated since providers should do this instead! + // Tries built in ID -> ProviderUrl + /* for (api in syncApis) { if (url.contains(api.mainUrl)) { val otherApi = when (api.name) { @@ -42,8 +39,10 @@ object SyncRedirector { // } } } + */ // Tries provider solution + // This goes through all sync ids and finds supported id by said provider return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) -> if (providerApi.supportedSyncNames.contains(syncName)) { syncRegex.find(url)?.value?.let { diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index ddb9f660..5f6b02a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -3,9 +3,18 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.ui.library.LibraryItem +enum class SyncIdName { + Anilist, + MyAnimeList, + Trakt, + Imdb +} + interface SyncAPI : OAuth2API { val mainUrl: String + val syncIdName: SyncIdName + /** -1 -> None 0 -> Watching diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt index 04e15f55..dea58434 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -12,6 +12,7 @@ class SyncRepo(private val repo: SyncAPI) { val icon = repo.icon val mainUrl = repo.mainUrl val requiresLogin = repo.requiresLogin + val syncIdName = repo.syncIdName suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource { return safeApiCall { repo.score(id, status) } @@ -37,5 +38,7 @@ class SyncRepo(private val repo: SyncAPI) { return normalSafeApiCall { repo.loginInfo() != null } ?: false } - fun getIdFromUrl(url: String): String = repo.getIdFromUrl(url) + fun getIdFromUrl(url: String): String? = normalSafeApiCall { + repo.getIdFromUrl(url) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 0e58720d..edc82746 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.library.LibraryItem import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery @@ -29,6 +30,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { override val icon = R.drawable.ic_anilist_icon override val requiresLogin = false override val createAccountUrl = "$mainUrl/signup" + override val syncIdName = SyncIdName.Anilist override fun loginInfo(): AuthAPI.LoginInfo? { // context.getUser(true)?. @@ -603,6 +605,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { ?: this.media.synonyms.firstOrNull() ?: "", "https://anilist.co/anime/${this.media.id}/", + this.media.id.toString(), listName?.lowercase()?.capitalize() ?: return null, this.progress, this.media.episodes, diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 8f734856..c202e7bc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.library.LibraryItem import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery @@ -35,6 +36,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { val apiUrl = "https://api.myanimelist.net" override val icon = R.drawable.mal_logo override val requiresLogin = false + override val syncIdName = SyncIdName.MyAnimeList override val createAccountUrl = "$mainUrl/register.php" @@ -388,6 +390,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return LibraryItem( this.node.title, "https://myanimelist.net/anime/${this.node.id}/", + this.node.id.toString(), this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE", this.list_status?.num_episodes_watched, this.node.num_episodes, @@ -395,7 +398,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { "MAL", TvType.Anime, this.node.main_picture?.large ?: this.node.main_picture?.medium, - null, null, null + null, + null, ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index 0d04f9f1..cbc48d41 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -9,15 +9,35 @@ import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.fragment.app.activityViewModels import com.google.android.material.tabs.TabLayoutMediator +import com.lagradost.cloudstream3.APIHolder.allProviders +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import kotlinx.android.synthetic.main.fragment_library.* +const val LIBRARY_FOLDER = "library_folder" + + +enum class LibraryOpenerType { + Provider, + Browser, +} + +/** Used to store how the user wants to open said poster */ +class LibraryOpener( + val openType: LibraryOpenerType, + val data: String?, +) + class LibraryFragment : Fragment() { companion object { @@ -27,8 +47,7 @@ class LibraryFragment : Fragment() { private val libraryViewModel: LibraryViewModel by activityViewModels() override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_library, container, false) } @@ -38,11 +57,13 @@ class LibraryFragment : Fragment() { context?.fixPaddingStatusbar(library_root) sort_fab?.setOnClickListener { - val methods = libraryViewModel.sortingMethods - .map { txt(it.stringRes).asString(context ?: view.context) } + val methods = libraryViewModel.sortingMethods.map { + txt(it.stringRes).asString( + context ?: view.context + ) + } - activity?.showBottomDialog( - methods, + activity?.showBottomDialog(methods, libraryViewModel.sortingMethods.indexOf(libraryViewModel.currentSortingMethod), "Sort by", false, @@ -50,8 +71,7 @@ class LibraryFragment : Fragment() { { val method = libraryViewModel.sortingMethods[it] libraryViewModel.sort(method) - } - ) + }) } main_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { @@ -72,18 +92,75 @@ class LibraryFragment : Fragment() { val items = libraryViewModel.availableApiNames val currentItem = libraryViewModel.currentApiName.value - activity?.showBottomDialog( - items, + activity?.showBottomDialog(items, items.indexOf(currentItem), "Select library", false, - {} - ) { + {}) { val selectedItem = items.getOrNull(it) ?: return@showBottomDialog libraryViewModel.switchList(selectedItem) } } + + /** + * Shows a plugin selection dialogue and saves the response + **/ + fun showPluginSelectionDialog(key: String, syncId: SyncIdName) { + val availableProviders = allProviders.filter { + it.supportedSyncNames.contains(syncId) + }.map { it.name } + + val baseOptions = listOf(LibraryOpenerType.Browser.name) + + val items = baseOptions + availableProviders + + val savedSelection = getKey(LIBRARY_FOLDER, key) + val selectedIndex = + when { + savedSelection == null -> -1 + // If provider + savedSelection.openType == LibraryOpenerType.Provider + && savedSelection.data != null -> { + availableProviders.indexOf(savedSelection.data).takeIf { it != -1 } + ?.plus(baseOptions.size) ?: -1 + } + // Else base option + else -> baseOptions.indexOf(savedSelection.openType.name) + } + + activity?.showBottomDialog( + items, + selectedIndex, + "Open with", + true, + {}, + ) { + val savedData = if (it < baseOptions.size) { + LibraryOpener( + LibraryOpenerType.valueOf(baseOptions[it]), + null + ) + } else { + LibraryOpener( + LibraryOpenerType.Provider, + items[it] + ) + } + + setKey( + LIBRARY_FOLDER, + key, + savedData, + ) + } + } + + provider_selector?.setOnClickListener { + val syncName = libraryViewModel.currentSyncApi?.syncIdName ?: return@setOnClickListener + showPluginSelectionDialog(syncName.name, syncName) + } + viewpager?.setPageTransformer(LibraryScrollTransformer()) viewpager?.adapter = viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean -> @@ -92,10 +169,27 @@ class LibraryFragment : Fragment() { } else { sort_fab?.extend() } - }) { searchClickCallback -> + }) callback@{ searchClickCallback -> + + // To prevent future accidents + debugAssert({ + searchClickCallback.card !is LibraryItem + }, { + "searchClickCallback ${searchClickCallback.card} is not a LibraryItem" + }) + val syncId = (searchClickCallback.card as LibraryItem).syncId + println("SEARCH CLICK $searchClickCallback") - if (searchClickCallback.action == SEARCH_ACTION_LOAD) { - activity?.loadSearchResult(searchClickCallback.card) + when (searchClickCallback.action) { + SEARCH_ACTION_SHOW_METADATA -> { + val syncName = + libraryViewModel.currentSyncApi?.syncIdName ?: return@callback + showPluginSelectionDialog(syncId, syncName) + } + SEARCH_ACTION_LOAD -> { + val savedSelection = getKey(LIBRARY_FOLDER, syncId) + activity?.loadSearchResult(searchClickCallback.card) + } } } viewpager?.offscreenPageLimit = 2 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index 9b9afc22..8548bf5c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -27,7 +27,10 @@ class LibraryViewModel : ViewModel() { val currentApiName: LiveData = _currentApiName private val availableSyncApis = SyncApis.filter { it.hasAccount() } - private var currentSyncApi = availableSyncApis.firstOrNull() + + // TODO REMEMBER SELECTION + var currentSyncApi = availableSyncApis.firstOrNull() + private set val availableApiNames: List = availableSyncApis.map { it.name } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index fbb3ec1e..f4f8369c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -42,6 +42,7 @@ data class Page( data class LibraryItem( override val name: String, override val url: String, + val syncId: String, val listName: String, val episodesCompleted: Int?, val episodesTotal: Int?, @@ -51,8 +52,8 @@ data class LibraryItem( override var type: TvType?, override var posterUrl: String?, override var posterHeaders: Map?, - override var id: Int?, override var quality: SearchQuality?, + override var id: Int? = null, ) : SearchResponse @@ -90,7 +91,6 @@ class ViewpagerAdapter( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> - println("DOWN ${(scrollY - oldScrollY)}") val diff = scrollY - oldScrollY if (diff == 0) return@setOnScrollChangeListener diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index c2523931..bbe5bac6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -15,7 +15,9 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx import kotlinx.android.synthetic.main.search_result_compact.view.* import kotlin.math.roundToInt +/** Click */ const val SEARCH_ACTION_LOAD = 0 +/** Long press */ const val SEARCH_ACTION_SHOW_METADATA = 1 const val SEARCH_ACTION_PLAY_FILE = 2 const val SEARCH_ACTION_FOCUSED = 4 diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 3c0da48e..0fa35e3b 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -13,52 +13,69 @@ android:layout_height="wrap_content" android:background="?attr/primaryGrayBackground"> - - - - - - - + android:layout_height="wrap_content" + android:orientation="horizontal"> - + + + + + + + + + + +