forked from recloudstream/cloudstream
More library fixes
This commit is contained in:
parent
c4afb5e673
commit
daaab9d35b
11 changed files with 93 additions and 32 deletions
|
@ -13,6 +13,10 @@ enum class SyncIdName {
|
|||
}
|
||||
|
||||
interface SyncAPI : OAuth2API {
|
||||
/**
|
||||
* Set this to true if the user updates something on the list like watch status or score
|
||||
**/
|
||||
var requireLibraryRefresh: Boolean
|
||||
val mainUrl: String
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,11 @@ class SyncRepo(private val repo: SyncAPI) {
|
|||
val mainUrl = repo.mainUrl
|
||||
val requiresLogin = repo.requiresLogin
|
||||
val syncIdName = repo.syncIdName
|
||||
var requireLibraryRefresh: Boolean
|
||||
get() = repo.requireLibraryRefresh
|
||||
set(value) {
|
||||
repo.requireLibraryRefresh = value
|
||||
}
|
||||
|
||||
suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> {
|
||||
return safeApiCall { repo.score(id, status) }
|
||||
|
|
|
@ -28,6 +28,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
override val key = "6871"
|
||||
override val redirectUrl = "anilistlogin"
|
||||
override val idPrefix = "anilist"
|
||||
override var requireLibraryRefresh = true
|
||||
override var mainUrl = "https://anilist.co"
|
||||
override val icon = R.drawable.ic_anilist_icon
|
||||
override val requiresLogin = false
|
||||
|
@ -47,6 +48,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
override fun logOut() {
|
||||
requireLibraryRefresh = true
|
||||
removeAccountKeys()
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
setKey(accountId, ANILIST_TOKEN_KEY, token)
|
||||
setKey(ANILIST_SHOULD_UPDATE_LIST, true)
|
||||
val user = getUser()
|
||||
requireLibraryRefresh = true
|
||||
return user != null
|
||||
}
|
||||
|
||||
|
@ -173,7 +176,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
status.watchedEpisodes
|
||||
)
|
||||
).also {
|
||||
requireLibraryRefresh = requireLibraryRefresh || it
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -295,15 +300,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val shows = searchShows(name.replace(blackListRegex, ""))
|
||||
|
||||
shows?.data?.Page?.media?.find {
|
||||
malId ?: "NONE" == it.idMal.toString()
|
||||
(malId ?: "NONE") == it.idMal.toString()
|
||||
}?.let { return it }
|
||||
|
||||
val filtered =
|
||||
shows?.data?.Page?.media?.filter {
|
||||
(
|
||||
it.startDate.year ?: year.toString() == year.toString()
|
||||
|| year == null
|
||||
)
|
||||
(((it.startDate.year ?: year.toString()) == year.toString()
|
||||
|| year == null))
|
||||
}
|
||||
filtered?.forEach {
|
||||
it.title.romaji?.let { romaji ->
|
||||
|
@ -529,7 +532,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
app.post(
|
||||
"https://graphql.anilist.co/",
|
||||
headers = mapOf(
|
||||
"Authorization" to "Bearer " + (getAuth() ?: return@suspendSafeApiCall null),
|
||||
"Authorization" to "Bearer " + (getAuth()
|
||||
?: return@suspendSafeApiCall null),
|
||||
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
||||
),
|
||||
cacheTime = 0,
|
||||
|
@ -722,9 +726,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
"""
|
||||
val text = postApi(query).also {
|
||||
println("REPONSE $it")
|
||||
}
|
||||
val text = postApi(query)
|
||||
return text?.toKotlinObject()
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class LocalList : SyncAPI {
|
|||
override val requiresLogin = false
|
||||
override val createAccountUrl: Nothing? = null
|
||||
override val idPrefix = "local"
|
||||
override var requireLibraryRefresh = true
|
||||
|
||||
override fun loginInfo(): AuthAPI.LoginInfo {
|
||||
return AuthAPI.LoginInfo(
|
||||
|
|
|
@ -33,14 +33,15 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
override val redirectUrl = "mallogin"
|
||||
override val idPrefix = "mal"
|
||||
override var mainUrl = "https://myanimelist.net"
|
||||
val apiUrl = "https://api.myanimelist.net"
|
||||
private val apiUrl = "https://api.myanimelist.net"
|
||||
override val icon = R.drawable.mal_logo
|
||||
override val requiresLogin = false
|
||||
override val syncIdName = SyncIdName.MyAnimeList
|
||||
|
||||
override var requireLibraryRefresh = true
|
||||
override val createAccountUrl = "$mainUrl/register.php"
|
||||
|
||||
override fun logOut() {
|
||||
requireLibraryRefresh = true
|
||||
removeAccountKeys()
|
||||
}
|
||||
|
||||
|
@ -93,7 +94,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
status.watchedEpisodes
|
||||
)
|
||||
).also {
|
||||
requireLibraryRefresh = requireLibraryRefresh || it
|
||||
}
|
||||
}
|
||||
|
||||
data class MalAnime(
|
||||
|
@ -311,9 +314,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
setKey(accountId, MAL_UNIXTIME_KEY, (token.expires_in + unixTime))
|
||||
setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token)
|
||||
setKey(accountId, MAL_TOKEN_KEY, token.access_token)
|
||||
requireLibraryRefresh = true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,7 +336,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
).text
|
||||
storeToken(res)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,7 +437,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return getKey(MAL_CACHED_LIST) as? Array<Data>
|
||||
}
|
||||
|
||||
suspend fun getMalAnimeListSmart(): Array<Data>? {
|
||||
private suspend fun getMalAnimeListSmart(): Array<Data>? {
|
||||
if (getAuth() == null) return null
|
||||
return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) {
|
||||
val list = getMalAnimeList()
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
|
@ -26,6 +27,7 @@ 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.loadResult
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||
import kotlinx.android.synthetic.main.fragment_library.*
|
||||
|
@ -264,8 +266,11 @@ class LibraryFragment : Fragment() {
|
|||
}
|
||||
|
||||
viewpager?.offscreenPageLimit = 2
|
||||
viewpager?.reduceDragSensitivity()
|
||||
|
||||
observe(libraryViewModel.pages) { pages ->
|
||||
empty_list_textview?.isVisible = pages.all { it.items.isEmpty() }
|
||||
|
||||
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
||||
// Using notifyItemRangeChanged keeps the animations when sorting
|
||||
viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0)
|
||||
|
|
|
@ -4,6 +4,8 @@ import androidx.annotation.StringRes
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||
|
@ -20,20 +22,29 @@ enum class ListSorting(@StringRes val stringRes: Int) {
|
|||
AlphabeticalZ(R.string.sort_alphabetical_z),
|
||||
}
|
||||
|
||||
const val LAST_SYNC_API_KEY = "last_sync_api"
|
||||
|
||||
class LibraryViewModel : ViewModel() {
|
||||
private val _pages: MutableLiveData<List<SyncAPI.Page>> = MutableLiveData(emptyList())
|
||||
private val _pages: MutableLiveData<List<SyncAPI.Page>> = MutableLiveData(null)
|
||||
val pages: LiveData<List<SyncAPI.Page>> = _pages
|
||||
|
||||
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
||||
val currentApiName: LiveData<String> = _currentApiName
|
||||
|
||||
private val availableSyncApis = SyncApis.filter { it.hasAccount() }
|
||||
private val availableSyncApis
|
||||
get() = SyncApis.filter { it.hasAccount() }
|
||||
|
||||
// TODO REMEMBER SELECTION
|
||||
var currentSyncApi = availableSyncApis.firstOrNull()
|
||||
private set
|
||||
var currentSyncApi = availableSyncApis.let { allApis ->
|
||||
val lastSelection = getKey<String>(LAST_SYNC_API_KEY)
|
||||
availableSyncApis.firstOrNull { it.name == lastSelection } ?: allApis.firstOrNull()
|
||||
}
|
||||
private set(value) {
|
||||
field = value
|
||||
setKey(LAST_SYNC_API_KEY, field?.name)
|
||||
}
|
||||
|
||||
val availableApiNames: List<String> = availableSyncApis.map { it.name }
|
||||
val availableApiNames: List<String>
|
||||
get() = availableSyncApis.map { it.name }
|
||||
|
||||
val sortingMethods = listOf(
|
||||
ListSorting.RatingHigh,
|
||||
|
@ -64,15 +75,19 @@ class LibraryViewModel : ViewModel() {
|
|||
|
||||
fun reloadPages(forceReload: Boolean) {
|
||||
// Only skip loading if its not forced and pages is not empty
|
||||
if (!forceReload && pages.value?.isNotEmpty() == true) return
|
||||
if (!forceReload && pages.value?.isNotEmpty() == true &&
|
||||
currentSyncApi?.requireLibraryRefresh != true
|
||||
) return
|
||||
|
||||
ioSafe {
|
||||
currentSyncApi?.let { repo ->
|
||||
_currentApiName.postValue(repo.name)
|
||||
val library = (repo.getPersonalLibrary() as? Resource.Success)?.value ?: return@let
|
||||
repo.requireLibraryRefresh = false
|
||||
|
||||
val listSubset = library.allLibraryItems.groupBy { it.listName }
|
||||
val allLists = library.allListNames.associateWith { emptyList<SyncAPI.LibraryItem>() }
|
||||
val allLists =
|
||||
library.allListNames.associateWith { emptyList<SyncAPI.LibraryItem>() }
|
||||
|
||||
val filledLists = allLists + listSubset
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.tvprovider.media.tv.*
|
||||
import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.cast.framework.CastState
|
||||
|
@ -166,6 +167,18 @@ object AppUtils {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/67441735/13746422
|
||||
fun ViewPager2.reduceDragSensitivity(f: Int = 4) {
|
||||
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
|
||||
recyclerViewField.isAccessible = true
|
||||
val recyclerView = recyclerViewField.get(this) as RecyclerView
|
||||
|
||||
val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
|
||||
touchSlopField.isAccessible = true
|
||||
val touchSlop = touchSlopField.get(recyclerView) as Int
|
||||
touchSlopField.set(recyclerView, touchSlop * f) // "8" was obtained experimentally
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun getAllWatchNextPrograms(context: Context): Set<Long> {
|
||||
val COLUMN_WATCH_NEXT_ID_INDEX = 0
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.DubStatus
|
|||
import com.lagradost.cloudstream3.SearchQuality
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.result.VideoWatchState
|
||||
|
@ -195,6 +196,7 @@ object DataStoreHelper {
|
|||
fun setBookmarkedData(id: Int?, data: BookmarkedData) {
|
||||
if (id == null) return
|
||||
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data)
|
||||
AccountManager.localListApi.requireLibraryRefresh = true
|
||||
}
|
||||
|
||||
fun getBookmarkedData(id: Int?): BookmarkedData? {
|
||||
|
|
|
@ -7,6 +7,17 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_list_textview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="30dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/empty_library_message"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -16,13 +27,14 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
app:layout_scrollFlags="scroll|enterAlways">
|
||||
|
||||
<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"
|
||||
|
@ -62,15 +74,14 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/list_selector"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
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:padding="10dp"
|
||||
android:src="@drawable/ic_baseline_filter_list_24"
|
||||
app:tint="?attr/textColor" />
|
||||
|
||||
|
@ -123,10 +134,8 @@
|
|||
android:background="?attr/primaryGrayBackground"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:focusable="false"
|
||||
|
||||
android:paddingHorizontal="5dp"
|
||||
app:layout_scrollFlags="noScroll"
|
||||
app:tabBackground="?attr/primaryGrayBackground"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicator="@drawable/indicator_background"
|
||||
app:tabIndicatorColor="@color/textColor"
|
||||
|
|
|
@ -627,4 +627,5 @@
|
|||
<string name="sort_alphabetical_z">Alphabetical (Z to A)</string>
|
||||
<string name="select_library">Select Library</string>
|
||||
<string name="open_with">Open with</string>
|
||||
<string name="empty_library_message">Looks like your library is empty :(\nLogin to a library account or add shows to your local library</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue