forked from recloudstream/cloudstream
More work on local list support
This commit is contained in:
parent
7cd6dd3fe6
commit
019e9a0c4f
11 changed files with 143 additions and 109 deletions
|
@ -1,7 +1,8 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||||
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
|
|
||||||
enum class SyncIdName {
|
enum class SyncIdName {
|
||||||
Anilist,
|
Anilist,
|
||||||
|
@ -37,9 +38,9 @@ interface SyncAPI : OAuth2API {
|
||||||
|
|
||||||
suspend fun search(name: String): List<SyncSearchResult>?
|
suspend fun search(name: String): List<SyncSearchResult>?
|
||||||
|
|
||||||
suspend fun getPersonalLibrary(): List<LibraryItem>?
|
suspend fun getPersonalLibrary(): LibraryMetadata?
|
||||||
|
|
||||||
fun getIdFromUrl(url : String) : String
|
fun getIdFromUrl(url: String): String
|
||||||
|
|
||||||
data class SyncSearchResult(
|
data class SyncSearchResult(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
|
@ -59,7 +60,7 @@ interface SyncAPI : OAuth2API {
|
||||||
val score: Int?,
|
val score: Int?,
|
||||||
val watchedEpisodes: Int?,
|
val watchedEpisodes: Int?,
|
||||||
var isFavorite: Boolean? = null,
|
var isFavorite: Boolean? = null,
|
||||||
var maxEpisodes : Int? = null,
|
var maxEpisodes: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SyncResult(
|
data class SyncResult(
|
||||||
|
@ -80,9 +81,9 @@ interface SyncAPI : OAuth2API {
|
||||||
var genres: List<String>? = null,
|
var genres: List<String>? = null,
|
||||||
var synonyms: List<String>? = null,
|
var synonyms: List<String>? = null,
|
||||||
var trailers: List<String>? = null,
|
var trailers: List<String>? = null,
|
||||||
var isAdult : Boolean? = null,
|
var isAdult: Boolean? = null,
|
||||||
var posterUrl: String? = null,
|
var posterUrl: String? = null,
|
||||||
var backgroundPosterUrl : String? = null,
|
var backgroundPosterUrl: String? = null,
|
||||||
|
|
||||||
/** In unixtime */
|
/** In unixtime */
|
||||||
var startDate: Long? = null,
|
var startDate: Long? = null,
|
||||||
|
@ -93,4 +94,53 @@ interface SyncAPI : OAuth2API {
|
||||||
var prevSeason: SyncSearchResult? = null,
|
var prevSeason: SyncSearchResult? = null,
|
||||||
var actors: List<ActorData>? = null,
|
var actors: List<ActorData>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
data class Page(
|
||||||
|
val title: String, var items: List<LibraryItem>
|
||||||
|
) {
|
||||||
|
fun sort(method: ListSorting?, query: String? = null) {
|
||||||
|
items = when (method) {
|
||||||
|
ListSorting.Query ->
|
||||||
|
if (query != null) {
|
||||||
|
items.sortedBy {
|
||||||
|
-FuzzySearch.partialRatio(
|
||||||
|
query.lowercase(), it.name.lowercase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else items
|
||||||
|
ListSorting.RatingHigh -> items.sortedBy { -(it.personalRating ?: 0) }
|
||||||
|
ListSorting.RatingLow -> items.sortedBy { (it.personalRating ?: 0) }
|
||||||
|
ListSorting.AlphabeticalA -> items.sortedBy { it.name }
|
||||||
|
ListSorting.AlphabeticalZ -> items.sortedBy { it.name }.reversed()
|
||||||
|
else -> items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LibraryMetadata(
|
||||||
|
/** List of all available pages, useful to show empty pages
|
||||||
|
* if the user has no entry on that page */
|
||||||
|
val allListNames: List<String>,
|
||||||
|
/** Not necessarily sorted list of all library items, will be grouped by listName */
|
||||||
|
val allLibraryItems: List<LibraryItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class LibraryItem(
|
||||||
|
override val name: String,
|
||||||
|
override val url: String,
|
||||||
|
/** Unique unchanging string used for data storage */
|
||||||
|
val syncId: String,
|
||||||
|
val listName: String,
|
||||||
|
val episodesCompleted: Int?,
|
||||||
|
val episodesTotal: Int?,
|
||||||
|
/** Out of 100 */
|
||||||
|
val personalRating: Int?,
|
||||||
|
override val apiName: String,
|
||||||
|
override var type: TvType?,
|
||||||
|
override var posterUrl: String?,
|
||||||
|
override var posterHeaders: Map<String, String>?,
|
||||||
|
override var quality: SearchQuality?,
|
||||||
|
override var id: Int? = null,
|
||||||
|
) : SearchResponse
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@ import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
|
||||||
|
|
||||||
class SyncRepo(private val repo: SyncAPI) {
|
class SyncRepo(private val repo: SyncAPI) {
|
||||||
val idPrefix = repo.idPrefix
|
val idPrefix = repo.idPrefix
|
||||||
|
@ -30,7 +29,7 @@ class SyncRepo(private val repo: SyncAPI) {
|
||||||
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
|
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPersonalLibrary(): Resource<List<LibraryItem>> {
|
suspend fun getPersonalLibrary(): Resource<SyncAPI.LibraryMetadata> {
|
||||||
return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() }
|
return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
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.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
@ -598,8 +597,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
@JsonProperty("private") val private: Boolean,
|
@JsonProperty("private") val private: Boolean,
|
||||||
@JsonProperty("media") val media: Media
|
@JsonProperty("media") val media: Media
|
||||||
) {
|
) {
|
||||||
fun toLibraryItem(listName: String?): LibraryItem? {
|
fun toLibraryItem(listName: String?): SyncAPI.LibraryItem? {
|
||||||
return LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
// English title first
|
// English title first
|
||||||
this.media.title.english ?: this.media.title.romaji
|
this.media.title.english ?: this.media.title.romaji
|
||||||
?: this.media.synonyms.firstOrNull()
|
?: this.media.synonyms.firstOrNull()
|
||||||
|
@ -612,7 +611,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
this.score,
|
this.score,
|
||||||
"AniList",
|
"AniList",
|
||||||
TvType.Anime,
|
TvType.Anime,
|
||||||
this.media.coverImage.extraLarge ?: 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
|
||||||
|
@ -653,14 +653,17 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
|
||||||
return getAniListAnimeListSmart()?.map {
|
return SyncAPI.LibraryMetadata(
|
||||||
it.entries.mapNotNull { entry ->
|
emptyList(),
|
||||||
entry.toLibraryItem(
|
getAniListAnimeListSmart()?.map {
|
||||||
entry.status ?: it.status
|
it.entries.mapNotNull { entry ->
|
||||||
)
|
entry.toLibraryItem(
|
||||||
}
|
entry.status ?: it.status
|
||||||
}?.flatten()
|
)
|
||||||
|
}
|
||||||
|
}?.flatten() ?: emptyList()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getFullAniListList(): FullAnilistList? {
|
private suspend fun getFullAniListList(): FullAnilistList? {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders.providers
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||||
|
@ -17,8 +19,12 @@ class LocalList : SyncAPI {
|
||||||
override val createAccountUrl: Nothing? = null
|
override val createAccountUrl: Nothing? = null
|
||||||
override val idPrefix = "local"
|
override val idPrefix = "local"
|
||||||
|
|
||||||
override fun loginInfo(): AuthAPI.LoginInfo? {
|
override fun loginInfo(): AuthAPI.LoginInfo {
|
||||||
return null
|
return AuthAPI.LoginInfo(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logOut() {
|
override fun logOut() {
|
||||||
|
@ -52,18 +58,29 @@ class LocalList : SyncAPI {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPersonalLibrary(): List<LibraryItem> {
|
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata? {
|
||||||
val watchStatusIds = ioWork {
|
val watchStatusIds = ioWork {
|
||||||
getAllWatchStateIds()?.map { id ->
|
getAllWatchStateIds()?.map { id ->
|
||||||
Pair(id, getResultWatchState(id))
|
Pair(id, getResultWatchState(id))
|
||||||
}
|
}
|
||||||
}?.distinctBy { it.first } ?: return emptyList()
|
}?.distinctBy { it.first } ?: return null
|
||||||
|
|
||||||
return ioWork {
|
val list = ioWork {
|
||||||
watchStatusIds.mapNotNull {
|
watchStatusIds.mapNotNull {
|
||||||
getBookmarkedData(it.first)?.toLibraryItem(it.second)
|
getBookmarkedData(it.first)?.toLibraryItem(it.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return SyncAPI.LibraryMetadata(
|
||||||
|
WatchType.values().mapNotNull {
|
||||||
|
// None is not something to display
|
||||||
|
if (it == WatchType.NONE) return@mapNotNull null
|
||||||
|
|
||||||
|
// Dirty hack for context!
|
||||||
|
txt(it.stringRes).asStringNull(AcraApplication.context)
|
||||||
|
},
|
||||||
|
list
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIdFromUrl(url: String): String {
|
override fun getIdFromUrl(url: String): String {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
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.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||||
|
@ -386,8 +385,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
@JsonProperty("node") val node: Node,
|
@JsonProperty("node") val node: Node,
|
||||||
@JsonProperty("list_status") val list_status: ListStatus?,
|
@JsonProperty("list_status") val list_status: ListStatus?,
|
||||||
) {
|
) {
|
||||||
fun toLibraryItem(): LibraryItem {
|
fun toLibraryItem(): SyncAPI.LibraryItem {
|
||||||
return LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
this.node.title,
|
this.node.title,
|
||||||
"https://myanimelist.net/anime/${this.node.id}/",
|
"https://myanimelist.net/anime/${this.node.id}/",
|
||||||
this.node.id.toString(),
|
this.node.id.toString(),
|
||||||
|
@ -445,8 +444,11 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
|
||||||
return getMalAnimeListSmart()?.map { it.toLibraryItem() }
|
return SyncAPI.LibraryMetadata(
|
||||||
|
emptyList(),
|
||||||
|
getMalAnimeListSmart()?.map { it.toLibraryItem() } ?: emptyList()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getMalAnimeList(): Array<Data> {
|
private suspend fun getMalAnimeList(): Array<Data> {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
|
@ -126,7 +127,8 @@ class LibraryFragment : Fragment() {
|
||||||
// If provider
|
// If provider
|
||||||
savedSelection.openType == LibraryOpenerType.Provider
|
savedSelection.openType == LibraryOpenerType.Provider
|
||||||
&& savedSelection.providerData?.apiName != null -> {
|
&& savedSelection.providerData?.apiName != null -> {
|
||||||
availableProviders.indexOf(savedSelection.providerData.apiName).takeIf { it != -1 }
|
availableProviders.indexOf(savedSelection.providerData.apiName)
|
||||||
|
.takeIf { it != -1 }
|
||||||
?.plus(baseOptions.size) ?: -1
|
?.plus(baseOptions.size) ?: -1
|
||||||
}
|
}
|
||||||
// Else base option
|
// Else base option
|
||||||
|
@ -167,7 +169,7 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
viewpager?.adapter =
|
viewpager?.adapter =
|
||||||
viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean ->
|
viewpager.adapter ?: ViewpagerAdapter(mutableListOf(), { isScrollingDown: Boolean ->
|
||||||
if (isScrollingDown) {
|
if (isScrollingDown) {
|
||||||
sort_fab?.shrink()
|
sort_fab?.shrink()
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,11 +179,11 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
// To prevent future accidents
|
// To prevent future accidents
|
||||||
debugAssert({
|
debugAssert({
|
||||||
searchClickCallback.card !is LibraryItem
|
searchClickCallback.card !is SyncAPI.LibraryItem
|
||||||
}, {
|
}, {
|
||||||
"searchClickCallback ${searchClickCallback.card} is not a LibraryItem"
|
"searchClickCallback ${searchClickCallback.card} is not a LibraryItem"
|
||||||
})
|
})
|
||||||
val syncId = (searchClickCallback.card as LibraryItem).syncId
|
val syncId = (searchClickCallback.card as SyncAPI.LibraryItem).syncId
|
||||||
|
|
||||||
println("SEARCH CLICK $searchClickCallback")
|
println("SEARCH CLICK $searchClickCallback")
|
||||||
when (searchClickCallback.action) {
|
when (searchClickCallback.action) {
|
||||||
|
@ -200,7 +202,8 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
observe(libraryViewModel.pages) { pages ->
|
observe(libraryViewModel.pages) { pages ->
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
||||||
viewpager.adapter?.notifyItemChanged(viewpager?.currentItem ?: 0)
|
// Using notifyItemRangeChanged keeps the animations when sorting
|
||||||
|
viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0)
|
||||||
|
|
||||||
TabLayoutMediator(
|
TabLayoutMediator(
|
||||||
library_tab_layout,
|
library_tab_layout,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import com.lagradost.cloudstream3.R
|
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.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
|
||||||
enum class ListSorting(@StringRes val stringRes: Int) {
|
enum class ListSorting(@StringRes val stringRes: Int) {
|
||||||
|
@ -20,8 +21,8 @@ enum class ListSorting(@StringRes val stringRes: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LibraryViewModel : ViewModel() {
|
class LibraryViewModel : ViewModel() {
|
||||||
private val _pages: MutableLiveData<List<Page>> = MutableLiveData(emptyList())
|
private val _pages: MutableLiveData<List<SyncAPI.Page>> = MutableLiveData(emptyList())
|
||||||
val pages: LiveData<List<Page>> = _pages
|
val pages: LiveData<List<SyncAPI.Page>> = _pages
|
||||||
|
|
||||||
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
||||||
val currentApiName: LiveData<String> = _currentApiName
|
val currentApiName: LiveData<String> = _currentApiName
|
||||||
|
@ -65,15 +66,22 @@ class LibraryViewModel : ViewModel() {
|
||||||
fun loadPages() {
|
fun loadPages() {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
currentSyncApi?.let { repo ->
|
currentSyncApi?.let { repo ->
|
||||||
val list = (repo.getPersonalLibrary() as? Resource.Success)?.value
|
_currentApiName.postValue(repo.name)
|
||||||
val pages = (list ?: emptyList()).groupBy { it.listName }.map {
|
val library = (repo.getPersonalLibrary() as? Resource.Success)?.value ?: return@let
|
||||||
Page(
|
|
||||||
|
val listSubset = library.allLibraryItems.groupBy { it.listName }
|
||||||
|
val allLists = library.allListNames.associateWith { emptyList<SyncAPI.LibraryItem>() }
|
||||||
|
|
||||||
|
val filledLists = allLists + listSubset
|
||||||
|
|
||||||
|
val pages = filledLists.map {
|
||||||
|
SyncAPI.Page(
|
||||||
it.key,
|
it.key,
|
||||||
it.value
|
it.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
println("PAGES $pages")
|
||||||
_pages.postValue(pages)
|
_pages.postValue(pages)
|
||||||
_currentApiName.postValue(repo.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,17 @@ import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
|
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
|
||||||
|
|
||||||
class PageAdapter(
|
class PageAdapter(
|
||||||
override val items: MutableList<LibraryItem>,
|
override val items: MutableList<SyncAPI.LibraryItem>,
|
||||||
val clickCallback: (SearchClickCallback) -> Unit
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
) :
|
) :
|
||||||
AppUtils.DiffAdapter<LibraryItem>(items) {
|
AppUtils.DiffAdapter<SyncAPI.LibraryItem>(items) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return LibraryItemViewHolder(
|
return LibraryItemViewHolder(
|
||||||
|
@ -33,7 +34,7 @@ class PageAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
fun bind(item: LibraryItem, position: Int) {
|
fun bind(item: SyncAPI.LibraryItem, position: Int) {
|
||||||
SearchResultBuilder.bind(
|
SearchResultBuilder.bind(
|
||||||
this@PageAdapter.clickCallback,
|
this@PageAdapter.clickCallback,
|
||||||
item,
|
item,
|
||||||
|
|
|
@ -4,62 +4,16 @@ import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.widget.NestedScrollView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchQuality
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
|
||||||
import com.lagradost.cloudstream3.TvType
|
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
|
||||||
|
|
||||||
data class Page(
|
|
||||||
val title: String, var items: List<LibraryItem>
|
|
||||||
) {
|
|
||||||
fun sort(method: ListSorting?, query: String? = null) {
|
|
||||||
items = when (method) {
|
|
||||||
ListSorting.Query ->
|
|
||||||
if (query != null) {
|
|
||||||
items.sortedBy {
|
|
||||||
-FuzzySearch.partialRatio(
|
|
||||||
query.lowercase(), it.name.lowercase()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else items
|
|
||||||
ListSorting.RatingHigh -> items.sortedBy { -(it.personalRating ?: 0) }
|
|
||||||
ListSorting.RatingLow -> items.sortedBy { (it.personalRating ?: 0) }
|
|
||||||
ListSorting.AlphabeticalA -> items.sortedBy { it.name }
|
|
||||||
ListSorting.AlphabeticalZ -> items.sortedBy { it.name }.reversed()
|
|
||||||
else -> items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class LibraryItem(
|
|
||||||
override val name: String,
|
|
||||||
override val url: String,
|
|
||||||
/** Unique unchanging string used for data storage */
|
|
||||||
val syncId: String,
|
|
||||||
val listName: String,
|
|
||||||
val episodesCompleted: Int?,
|
|
||||||
val episodesTotal: Int?,
|
|
||||||
/** Out of 100 */
|
|
||||||
val personalRating: Int?,
|
|
||||||
override val apiName: String,
|
|
||||||
override var type: TvType?,
|
|
||||||
override var posterUrl: String?,
|
|
||||||
override var posterHeaders: Map<String, String>?,
|
|
||||||
override var quality: SearchQuality?,
|
|
||||||
override var id: Int? = null,
|
|
||||||
) : SearchResponse
|
|
||||||
|
|
||||||
|
|
||||||
class ViewpagerAdapter(
|
class ViewpagerAdapter(
|
||||||
var pages: List<Page>,
|
var pages: List<SyncAPI.Page>,
|
||||||
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
||||||
val clickCallback: (SearchClickCallback) -> Unit
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
@ -80,7 +34,7 @@ class ViewpagerAdapter(
|
||||||
|
|
||||||
inner class PageViewHolder(private val itemViewTest: View) :
|
inner class PageViewHolder(private val itemViewTest: View) :
|
||||||
RecyclerView.ViewHolder(itemViewTest) {
|
RecyclerView.ViewHolder(itemViewTest) {
|
||||||
fun bind(page: Page) {
|
fun bind(page: SyncAPI.Page) {
|
||||||
if (itemViewTest.page_recyclerview?.adapter == null) {
|
if (itemViewTest.page_recyclerview?.adapter == null) {
|
||||||
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList(), clickCallback)
|
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList(), clickCallback)
|
||||||
itemView.page_recyclerview?.spanCount =
|
itemView.page_recyclerview?.spanCount =
|
||||||
|
|
|
@ -277,18 +277,14 @@ object AppUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateList(newList: List<T>) {
|
fun updateList(newList: List<T>) {
|
||||||
val time = measureTimeMillis {
|
val diffResult = DiffUtil.calculateDiff(
|
||||||
|
GenericDiffCallback(this.items, newList)
|
||||||
|
)
|
||||||
|
|
||||||
val diffResult = DiffUtil.calculateDiff(
|
items.clear()
|
||||||
GenericDiffCallback(this.items, newList)
|
items.addAll(newList)
|
||||||
)
|
|
||||||
|
|
||||||
items.clear()
|
diffResult.dispatchUpdatesTo(this)
|
||||||
items.addAll(newList)
|
|
||||||
|
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
}
|
|
||||||
println("TIME TAKEn $time")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class GenericDiffCallback(
|
inner class GenericDiffCallback(
|
||||||
|
|
|
@ -11,10 +11,8 @@ import com.lagradost.cloudstream3.DubStatus
|
||||||
import com.lagradost.cloudstream3.SearchQuality
|
import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.providers.LocalList
|
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
|
||||||
|
|
||||||
const val VIDEO_POS_DUR = "video_pos_dur"
|
const val VIDEO_POS_DUR = "video_pos_dur"
|
||||||
const val RESULT_WATCH_STATE = "result_watch_state"
|
const val RESULT_WATCH_STATE = "result_watch_state"
|
||||||
|
@ -54,8 +52,8 @@ object DataStoreHelper {
|
||||||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
||||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
||||||
) : SearchResponse {
|
) : SearchResponse {
|
||||||
fun toLibraryItem(state: WatchType): LibraryItem {
|
fun toLibraryItem(state: WatchType): SyncAPI.LibraryItem {
|
||||||
return LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
url,
|
url,
|
||||||
|
@ -86,6 +84,9 @@ object DataStoreHelper {
|
||||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A datastore wide account for future implementations of a multiple account system
|
||||||
|
**/
|
||||||
private var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
private var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
||||||
|
|
||||||
fun getAllWatchStateIds(): List<Int>? {
|
fun getAllWatchStateIds(): List<Int>? {
|
||||||
|
|
Loading…
Reference in a new issue