WIP adding open with specified extension

This commit is contained in:
Blatzar 2022-12-28 20:26:59 +01:00
parent 2a2a0a26e7
commit 8cdc31ffcd
11 changed files with 203 additions and 69 deletions

View file

@ -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.*

View file

@ -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 {

View file

@ -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

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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,
)
}
}

View file

@ -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,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<LibraryOpener>(LIBRARY_FOLDER, syncId)
activity?.loadSearchResult(searchClickCallback.card)
}
}
}
viewpager?.offscreenPageLimit = 2

View file

@ -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 }

View file

@ -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

View file

@ -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

View file

@ -13,52 +13,69 @@
android:layout_height="wrap_content"
android:background="?attr/primaryGrayBackground">
<FrameLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible"
app:layout_scrollFlags="scroll|enterAlways">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
tools:ignore="RtlSymmetry">
</androidx.appcompat.widget.SearchView>
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/list_selector"
android:id="@+id/provider_selector"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:src="@drawable/ic_baseline_filter_list_24"
android:src="@drawable/ic_baseline_extension_24"
app:tint="?attr/textColor" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/search_background"
android:visibility="visible"
app:layout_scrollFlags="scroll|enterAlways">
<androidx.appcompat.widget.SearchView
android:id="@+id/main_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
android:inputType="text"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/search_filter"
android:paddingStart="-10dp"
app:iconifiedByDefault="false"
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
tools:ignore="RtlSymmetry">
</androidx.appcompat.widget.SearchView>
<ImageView
android:id="@+id/list_selector"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search"
android:src="@drawable/ic_baseline_filter_list_24"
app:tint="?attr/textColor" />
</FrameLayout>
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>