mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
WIP adding open with specified extension
This commit is contained in:
parent
2a2a0a26e7
commit
8cdc31ffcd
11 changed files with 203 additions and 69 deletions
|
@ -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.*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Boolean> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<LibraryOpener>(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,12 +169,29 @@ 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) {
|
||||
when (searchClickCallback.action) {
|
||||
SEARCH_ACTION_SHOW_METADATA -> {
|
||||
val syncName =
|
||||
libraryViewModel.currentSyncApi?.syncIdName ?: return@callback
|
||||
showPluginSelectionDialog(syncId, syncName)
|
||||
}
|
||||
SEARCH_ACTION_LOAD -> {
|
||||
val savedSelection = getKey<LibraryOpener>(LIBRARY_FOLDER, syncId)
|
||||
activity?.loadSearchResult(searchClickCallback.card)
|
||||
}
|
||||
}
|
||||
}
|
||||
viewpager?.offscreenPageLimit = 2
|
||||
|
||||
observe(libraryViewModel.pages) { pages ->
|
||||
|
|
|
@ -27,7 +27,10 @@ class LibraryViewModel : ViewModel() {
|
|||
val currentApiName: LiveData<String> = _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<String> = availableSyncApis.map { it.name }
|
||||
|
||||
|
|
|
@ -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<String, String>?,
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,6 +13,22 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryGrayBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/provider_selector"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/change_providers_img_des"
|
||||
android:src="@drawable/ic_baseline_extension_24"
|
||||
app:tint="?attr/textColor" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
|
@ -59,6 +75,7 @@
|
|||
app:tint="?attr/textColor" />
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
Loading…
Reference in a new issue