forked from recloudstream/cloudstream
Add list switcher
This commit is contained in:
parent
67a1c447ae
commit
2a2a0a26e7
4 changed files with 71 additions and 36 deletions
|
@ -1,12 +1,9 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders.providers
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
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.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.capitalize
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
|
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -141,7 +138,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
this.name,
|
this.name,
|
||||||
recMedia.id?.toString() ?: return@mapNotNull null,
|
recMedia.id?.toString() ?: return@mapNotNull null,
|
||||||
getUrlFromId(recMedia.id),
|
getUrlFromId(recMedia.id),
|
||||||
recMedia.coverImage?.large ?: recMedia.coverImage?.medium
|
recMedia.coverImage?.extraLarge ?: recMedia.coverImage?.large
|
||||||
|
?: recMedia.coverImage?.medium
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
trailers = when (season.trailer?.site?.lowercase()?.trim()) {
|
trailers = when (season.trailer?.site?.lowercase()?.trim()) {
|
||||||
|
@ -220,7 +218,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
romaji
|
romaji
|
||||||
}
|
}
|
||||||
idMal
|
idMal
|
||||||
coverImage { medium large }
|
coverImage { medium large extraLarge }
|
||||||
averageScore
|
averageScore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +231,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
format
|
format
|
||||||
id
|
id
|
||||||
idMal
|
idMal
|
||||||
coverImage { medium large }
|
coverImage { medium large extraLarge }
|
||||||
averageScore
|
averageScore
|
||||||
title {
|
title {
|
||||||
english
|
english
|
||||||
|
@ -569,7 +567,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
|
|
||||||
data class CoverImage(
|
data class CoverImage(
|
||||||
@JsonProperty("medium") val medium: String?,
|
@JsonProperty("medium") val medium: String?,
|
||||||
@JsonProperty("large") val large: String?
|
@JsonProperty("large") val large: String?,
|
||||||
|
@JsonProperty("extraLarge") val extraLarge: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Media(
|
data class Media(
|
||||||
|
@ -600,16 +599,17 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
fun toLibraryItem(listName: String?): LibraryItem? {
|
fun toLibraryItem(listName: String?): LibraryItem? {
|
||||||
return LibraryItem(
|
return LibraryItem(
|
||||||
// English title first
|
// English title first
|
||||||
this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull()
|
this.media.title.english ?: this.media.title.romaji
|
||||||
|
?: this.media.synonyms.firstOrNull()
|
||||||
?: "",
|
?: "",
|
||||||
"https://anilist.co/anime/${this.media.id}/",
|
"https://anilist.co/anime/${this.media.id}/",
|
||||||
listName ?: return null,
|
listName?.lowercase()?.capitalize() ?: return null,
|
||||||
this.progress,
|
this.progress,
|
||||||
this.media.episodes,
|
this.media.episodes,
|
||||||
this.score,
|
this.score,
|
||||||
"AniList",
|
"AniList",
|
||||||
TvType.Anime,
|
TvType.Anime,
|
||||||
this.media.coverImage.large ?: this.media.coverImage.medium,
|
this.media.coverImage.extraLarge ?: this.media.coverImage.large ?: this.media.coverImage.medium,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
|
@ -630,44 +630,44 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
|
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getAnilistListCached(): Array<Lists>? {
|
private fun getAniListListCached(): Array<Lists>? {
|
||||||
return getKey(ANILIST_CACHED_LIST) as? Array<Lists>
|
return getKey(ANILIST_CACHED_LIST) as? Array<Lists>
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAnilistAnimeListSmart(): Array<Lists>? {
|
private suspend fun getAniListAnimeListSmart(): Array<Lists>? {
|
||||||
if (getAuth() == null) return null
|
if (getAuth() == null) return null
|
||||||
|
|
||||||
if (checkToken()) return null
|
if (checkToken()) return null
|
||||||
return if (getKey(ANILIST_SHOULD_UPDATE_LIST, true) == true) {
|
return if (getKey(ANILIST_SHOULD_UPDATE_LIST, true) == true) {
|
||||||
val list = getFullAnilistList()?.data?.MediaListCollection?.lists?.toTypedArray()
|
val list = getFullAniListList()?.data?.MediaListCollection?.lists?.toTypedArray()
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
setKey(ANILIST_CACHED_LIST, list)
|
setKey(ANILIST_CACHED_LIST, list)
|
||||||
setKey(ANILIST_SHOULD_UPDATE_LIST, false)
|
setKey(ANILIST_SHOULD_UPDATE_LIST, false)
|
||||||
}
|
}
|
||||||
list
|
list
|
||||||
} else {
|
} else {
|
||||||
getAnilistListCached()
|
getAniListListCached()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
||||||
return getAnilistAnimeListSmart()?.map { it.entries.mapNotNull { entry -> entry.toLibraryItem(entry.status ?: it.status) } }?.flatten()
|
return getAniListAnimeListSmart()?.map {
|
||||||
|
it.entries.mapNotNull { entry ->
|
||||||
|
entry.toLibraryItem(
|
||||||
|
entry.status ?: it.status
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}?.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getFullAnilistList(): FullAnilistList? {
|
private suspend fun getFullAniListList(): FullAnilistList? {
|
||||||
var userID: Int? = null
|
|
||||||
/** WARNING ASSUMES ONE USER! **/
|
/** WARNING ASSUMES ONE USER! **/
|
||||||
getKeys(ANILIST_USER_KEY)?.forEach { key ->
|
|
||||||
getKey<AniListUser>(key, null)?.let {
|
|
||||||
userID = it.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val fixedUserID = userID ?: return null
|
val userID = getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.id ?: return null
|
||||||
val mediaType = "ANIME"
|
val mediaType = "ANIME"
|
||||||
|
|
||||||
val query = """
|
val query = """
|
||||||
query (${'$'}userID: Int = $fixedUserID, ${'$'}MEDIA: MediaType = $mediaType) {
|
query (${'$'}userID: Int = $userID, ${'$'}MEDIA: MediaType = $mediaType) {
|
||||||
MediaListCollection (userId: ${'$'}userID, type: ${'$'}MEDIA) {
|
MediaListCollection (userId: ${'$'}userID, type: ${'$'}MEDIA) {
|
||||||
lists {
|
lists {
|
||||||
status
|
status
|
||||||
|
@ -694,7 +694,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
english
|
english
|
||||||
romaji
|
romaji
|
||||||
}
|
}
|
||||||
coverImage { medium }
|
coverImage { extraLarge large medium }
|
||||||
synonyms
|
synonyms
|
||||||
nextAiringEpisode {
|
nextAiringEpisode {
|
||||||
timeUntilAiring
|
timeUntilAiring
|
||||||
|
@ -706,7 +706,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val text = postApi(query)
|
val text = postApi(query).also {
|
||||||
|
println("REPONSE $it")
|
||||||
|
}
|
||||||
return text?.toKotlinObject()
|
return text?.toKotlinObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,22 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
libraryViewModel.loadPages()
|
libraryViewModel.loadPages()
|
||||||
|
|
||||||
|
list_selector?.setOnClickListener {
|
||||||
|
val items = libraryViewModel.availableApiNames
|
||||||
|
val currentItem = libraryViewModel.currentApiName.value
|
||||||
|
|
||||||
|
activity?.showBottomDialog(
|
||||||
|
items,
|
||||||
|
items.indexOf(currentItem),
|
||||||
|
"Select library",
|
||||||
|
false,
|
||||||
|
{}
|
||||||
|
) {
|
||||||
|
val selectedItem = items.getOrNull(it) ?: return@showBottomDialog
|
||||||
|
libraryViewModel.switchList(selectedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
viewpager?.adapter =
|
viewpager?.adapter =
|
||||||
viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean ->
|
viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean ->
|
||||||
|
|
|
@ -8,7 +8,6 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
|
||||||
|
|
||||||
enum class ListSorting(@StringRes val stringRes: Int) {
|
enum class ListSorting(@StringRes val stringRes: Int) {
|
||||||
Query(R.string.none),
|
Query(R.string.none),
|
||||||
|
@ -27,8 +26,10 @@ class LibraryViewModel : ViewModel() {
|
||||||
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
||||||
val currentApiName: LiveData<String> = _currentApiName
|
val currentApiName: LiveData<String> = _currentApiName
|
||||||
|
|
||||||
private val listApis = SyncApis.filter { it.hasAccount() }
|
private val availableSyncApis = SyncApis.filter { it.hasAccount() }
|
||||||
private var currentApi = listApis.firstOrNull()
|
private var currentSyncApi = availableSyncApis.firstOrNull()
|
||||||
|
|
||||||
|
val availableApiNames: List<String> = availableSyncApis.map { it.name }
|
||||||
|
|
||||||
val sortingMethods = listOf(
|
val sortingMethods = listOf(
|
||||||
ListSorting.RatingHigh,
|
ListSorting.RatingHigh,
|
||||||
|
@ -42,8 +43,10 @@ class LibraryViewModel : ViewModel() {
|
||||||
var currentSortingMethod: ListSorting = sortingMethods.first()
|
var currentSortingMethod: ListSorting = sortingMethods.first()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun switchList() {
|
fun switchList(name: String) {
|
||||||
currentApi = listApis[(listApis.indexOf(currentApi) + 1) % listApis.size]
|
currentSyncApi = availableSyncApis[availableApiNames.indexOf(name)]
|
||||||
|
_currentApiName.postValue(currentSyncApi?.name)
|
||||||
|
|
||||||
loadPages()
|
loadPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ class LibraryViewModel : ViewModel() {
|
||||||
|
|
||||||
fun loadPages() {
|
fun loadPages() {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
currentApi?.let { repo ->
|
currentSyncApi?.let { repo ->
|
||||||
val list = (repo.getPersonalLibrary() as? Resource.Success)?.value
|
val list = (repo.getPersonalLibrary() as? Resource.Success)?.value
|
||||||
val pages = (list ?: emptyList()).groupBy { it.listName }.map {
|
val pages = (list ?: emptyList()).groupBy { it.listName }.map {
|
||||||
Page(
|
Page(
|
||||||
|
|
|
@ -34,8 +34,6 @@
|
||||||
android:nextFocusLeft="@id/nav_rail_view"
|
android:nextFocusLeft="@id/nav_rail_view"
|
||||||
|
|
||||||
android:nextFocusRight="@id/search_filter"
|
android:nextFocusRight="@id/search_filter"
|
||||||
android:nextFocusUp="@id/nav_rail_view"
|
|
||||||
android:nextFocusDown="@id/search_autofit_results"
|
|
||||||
android:paddingStart="-10dp"
|
android:paddingStart="-10dp"
|
||||||
app:iconifiedByDefault="false"
|
app:iconifiedByDefault="false"
|
||||||
app:queryBackground="@color/transparent"
|
app:queryBackground="@color/transparent"
|
||||||
|
@ -44,6 +42,22 @@
|
||||||
tools:ignore="RtlSymmetry">
|
tools:ignore="RtlSymmetry">
|
||||||
|
|
||||||
</androidx.appcompat.widget.SearchView>
|
</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>
|
</FrameLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
@ -76,7 +90,7 @@
|
||||||
style="@style/ExtendedFloatingActionButton"
|
style="@style/ExtendedFloatingActionButton"
|
||||||
android:text="Sort"
|
android:text="Sort"
|
||||||
android:textColor="?attr/textColor"
|
android:textColor="?attr/textColor"
|
||||||
app:icon="@drawable/ic_baseline_filter_list_24"
|
app:icon="@drawable/ic_baseline_sort_24"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue