More library fixes

This commit is contained in:
Blatzar 2023-01-24 20:27:22 +01:00
parent c4afb5e673
commit daaab9d35b
11 changed files with 93 additions and 32 deletions

View file

@ -13,6 +13,10 @@ enum class SyncIdName {
} }
interface SyncAPI : OAuth2API { 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 val mainUrl: String
/** /**

View file

@ -12,6 +12,11 @@ class SyncRepo(private val repo: SyncAPI) {
val mainUrl = repo.mainUrl val mainUrl = repo.mainUrl
val requiresLogin = repo.requiresLogin val requiresLogin = repo.requiresLogin
val syncIdName = repo.syncIdName 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> { suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> {
return safeApiCall { repo.score(id, status) } return safeApiCall { repo.score(id, status) }

View file

@ -28,6 +28,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override val key = "6871" override val key = "6871"
override val redirectUrl = "anilistlogin" override val redirectUrl = "anilistlogin"
override val idPrefix = "anilist" override val idPrefix = "anilist"
override var requireLibraryRefresh = true
override var mainUrl = "https://anilist.co" override var mainUrl = "https://anilist.co"
override val icon = R.drawable.ic_anilist_icon override val icon = R.drawable.ic_anilist_icon
override val requiresLogin = false override val requiresLogin = false
@ -47,6 +48,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
} }
override fun logOut() { override fun logOut() {
requireLibraryRefresh = true
removeAccountKeys() removeAccountKeys()
} }
@ -68,6 +70,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
setKey(accountId, ANILIST_TOKEN_KEY, token) setKey(accountId, ANILIST_TOKEN_KEY, token)
setKey(ANILIST_SHOULD_UPDATE_LIST, true) setKey(ANILIST_SHOULD_UPDATE_LIST, true)
val user = getUser() val user = getUser()
requireLibraryRefresh = true
return user != null return user != null
} }
@ -173,7 +176,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status), fromIntToAnimeStatus(status.status),
status.score, status.score,
status.watchedEpisodes status.watchedEpisodes
) ).also {
requireLibraryRefresh = requireLibraryRefresh || it
}
} }
companion object { companion object {
@ -295,15 +300,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val shows = searchShows(name.replace(blackListRegex, "")) val shows = searchShows(name.replace(blackListRegex, ""))
shows?.data?.Page?.media?.find { shows?.data?.Page?.media?.find {
malId ?: "NONE" == it.idMal.toString() (malId ?: "NONE") == it.idMal.toString()
}?.let { return it } }?.let { return it }
val filtered = val filtered =
shows?.data?.Page?.media?.filter { shows?.data?.Page?.media?.filter {
( (((it.startDate.year ?: year.toString()) == year.toString()
it.startDate.year ?: year.toString() == year.toString() || year == null))
|| year == null
)
} }
filtered?.forEach { filtered?.forEach {
it.title.romaji?.let { romaji -> it.title.romaji?.let { romaji ->
@ -529,7 +532,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
app.post( app.post(
"https://graphql.anilist.co/", "https://graphql.anilist.co/",
headers = mapOf( 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" if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
), ),
cacheTime = 0, cacheTime = 0,
@ -722,9 +726,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
} }
} }
""" """
val text = postApi(query).also { val text = postApi(query)
println("REPONSE $it")
}
return text?.toKotlinObject() return text?.toKotlinObject()
} }

View file

@ -19,6 +19,7 @@ class LocalList : SyncAPI {
override val requiresLogin = false override val requiresLogin = false
override val createAccountUrl: Nothing? = null override val createAccountUrl: Nothing? = null
override val idPrefix = "local" override val idPrefix = "local"
override var requireLibraryRefresh = true
override fun loginInfo(): AuthAPI.LoginInfo { override fun loginInfo(): AuthAPI.LoginInfo {
return AuthAPI.LoginInfo( return AuthAPI.LoginInfo(

View file

@ -33,14 +33,15 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
override val redirectUrl = "mallogin" override val redirectUrl = "mallogin"
override val idPrefix = "mal" override val idPrefix = "mal"
override var mainUrl = "https://myanimelist.net" 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 icon = R.drawable.mal_logo
override val requiresLogin = false override val requiresLogin = false
override val syncIdName = SyncIdName.MyAnimeList override val syncIdName = SyncIdName.MyAnimeList
override var requireLibraryRefresh = true
override val createAccountUrl = "$mainUrl/register.php" override val createAccountUrl = "$mainUrl/register.php"
override fun logOut() { override fun logOut() {
requireLibraryRefresh = true
removeAccountKeys() removeAccountKeys()
} }
@ -93,7 +94,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status), fromIntToAnimeStatus(status.status),
status.score, status.score,
status.watchedEpisodes status.watchedEpisodes
) ).also {
requireLibraryRefresh = requireLibraryRefresh || it
}
} }
data class MalAnime( 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_UNIXTIME_KEY, (token.expires_in + unixTime))
setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token) setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token)
setKey(accountId, MAL_TOKEN_KEY, token.access_token) setKey(accountId, MAL_TOKEN_KEY, token.access_token)
requireLibraryRefresh = true
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() logError(e)
} }
} }
@ -332,7 +336,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
).text ).text
storeToken(res) storeToken(res)
} catch (e: Exception) { } 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> return getKey(MAL_CACHED_LIST) as? Array<Data>
} }
suspend fun getMalAnimeListSmart(): Array<Data>? { private suspend fun getMalAnimeListSmart(): Array<Data>? {
if (getAuth() == null) return null if (getAuth() == null) return null
return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) { return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) {
val list = getMalAnimeList() val list = getMalAnimeList()

View file

@ -9,6 +9,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.lagradost.cloudstream3.APIHolder 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.ui.search.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult 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.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_library.* import kotlinx.android.synthetic.main.fragment_library.*
@ -264,8 +266,11 @@ class LibraryFragment : Fragment() {
} }
viewpager?.offscreenPageLimit = 2 viewpager?.offscreenPageLimit = 2
viewpager?.reduceDragSensitivity()
observe(libraryViewModel.pages) { pages -> observe(libraryViewModel.pages) { pages ->
empty_list_textview?.isVisible = pages.all { it.items.isEmpty() }
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages (viewpager.adapter as? ViewpagerAdapter)?.pages = pages
// Using notifyItemRangeChanged keeps the animations when sorting // Using notifyItemRangeChanged keeps the animations when sorting
viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0) viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0)

View file

@ -4,6 +4,8 @@ import androidx.annotation.StringRes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel 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.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
@ -20,20 +22,29 @@ enum class ListSorting(@StringRes val stringRes: Int) {
AlphabeticalZ(R.string.sort_alphabetical_z), AlphabeticalZ(R.string.sort_alphabetical_z),
} }
const val LAST_SYNC_API_KEY = "last_sync_api"
class LibraryViewModel : ViewModel() { 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 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
private val availableSyncApis = SyncApis.filter { it.hasAccount() } private val availableSyncApis
get() = SyncApis.filter { it.hasAccount() }
// TODO REMEMBER SELECTION var currentSyncApi = availableSyncApis.let { allApis ->
var currentSyncApi = availableSyncApis.firstOrNull() val lastSelection = getKey<String>(LAST_SYNC_API_KEY)
private set 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( val sortingMethods = listOf(
ListSorting.RatingHigh, ListSorting.RatingHigh,
@ -64,15 +75,19 @@ class LibraryViewModel : ViewModel() {
fun reloadPages(forceReload: Boolean) { fun reloadPages(forceReload: Boolean) {
// Only skip loading if its not forced and pages is not empty // 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 { ioSafe {
currentSyncApi?.let { repo -> currentSyncApi?.let { repo ->
_currentApiName.postValue(repo.name) _currentApiName.postValue(repo.name)
val library = (repo.getPersonalLibrary() as? Resource.Success)?.value ?: return@let val library = (repo.getPersonalLibrary() as? Resource.Success)?.value ?: return@let
repo.requireLibraryRefresh = false
val listSubset = library.allLibraryItems.groupBy { it.listName } 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 val filledLists = allLists + listSubset

View file

@ -33,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.tvprovider.media.tv.* import androidx.tvprovider.media.tv.*
import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor
import androidx.viewpager2.widget.ViewPager2
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState import com.google.android.gms.cast.framework.CastState
@ -166,6 +167,18 @@ object AppUtils {
return builder.build() 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") @SuppressLint("RestrictedApi")
fun getAllWatchNextPrograms(context: Context): Set<Long> { fun getAllWatchNextPrograms(context: Context): Set<Long> {
val COLUMN_WATCH_NEXT_ID_INDEX = 0 val COLUMN_WATCH_NEXT_ID_INDEX = 0

View file

@ -11,6 +11,7 @@ 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.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.ui.result.VideoWatchState
@ -195,6 +196,7 @@ object DataStoreHelper {
fun setBookmarkedData(id: Int?, data: BookmarkedData) { fun setBookmarkedData(id: Int?, data: BookmarkedData) {
if (id == null) return if (id == null) return
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data) setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data)
AccountManager.localListApi.requireLibraryRefresh = true
} }
fun getBookmarkedData(id: Int?): BookmarkedData? { fun getBookmarkedData(id: Int?): BookmarkedData? {

View file

@ -7,6 +7,17 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <com.google.android.material.appbar.AppBarLayout
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -16,13 +27,14 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
app:layout_scrollFlags="scroll|enterAlways">
<ImageView <ImageView
android:id="@+id/provider_selector" android:id="@+id/provider_selector"
android:layout_width="25dp" android:layout_width="25dp"
android:layout_height="25dp" android:layout_height="25dp"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des" android:contentDescription="@string/change_providers_img_des"
@ -62,15 +74,14 @@
<ImageView <ImageView
android:id="@+id/list_selector" android:id="@+id/list_selector"
android:layout_width="25dp" android:layout_width="45dp"
android:layout_height="25dp" android:layout_height="45dp"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:layout_margin="10dp"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des" android:contentDescription="@string/change_providers_img_des"
android:nextFocusLeft="@id/main_search" android:nextFocusLeft="@id/main_search"
android:nextFocusRight="@id/main_search" android:nextFocusRight="@id/main_search"
android:padding="10dp"
android:src="@drawable/ic_baseline_filter_list_24" android:src="@drawable/ic_baseline_filter_list_24"
app:tint="?attr/textColor" /> app:tint="?attr/textColor" />
@ -123,10 +134,8 @@
android:background="?attr/primaryGrayBackground" android:background="?attr/primaryGrayBackground"
android:descendantFocusability="blocksDescendants" android:descendantFocusability="blocksDescendants"
android:focusable="false" android:focusable="false"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
app:layout_scrollFlags="noScroll" app:layout_scrollFlags="noScroll"
app:tabBackground="?attr/primaryGrayBackground"
app:tabGravity="center" app:tabGravity="center"
app:tabIndicator="@drawable/indicator_background" app:tabIndicator="@drawable/indicator_background"
app:tabIndicatorColor="@color/textColor" app:tabIndicatorColor="@color/textColor"

View file

@ -627,4 +627,5 @@
<string name="sort_alphabetical_z">Alphabetical (Z to A)</string> <string name="sort_alphabetical_z">Alphabetical (Z to A)</string>
<string name="select_library">Select Library</string> <string name="select_library">Select Library</string>
<string name="open_with">Open with</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> </resources>