diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index ca562e88..ab9db4ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -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 /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt index 6331cb9d..85b877e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -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 { return safeApiCall { repo.score(id, status) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 02090fff..12091e4d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 8625e9eb..7a53e6a4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -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( diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 07bc2350..f0b5cc91 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -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 } - suspend fun getMalAnimeListSmart(): Array? { + private suspend fun getMalAnimeListSmart(): Array? { if (getAuth() == null) return null return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) { val list = getMalAnimeList() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index a6613497..936d0b65 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index 161604e7..1e443fd0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -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> = MutableLiveData(emptyList()) + private val _pages: MutableLiveData> = MutableLiveData(null) val pages: LiveData> = _pages private val _currentApiName: MutableLiveData = MutableLiveData("") val currentApiName: LiveData = _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(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 = availableSyncApis.map { it.name } + val availableApiNames: List + 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() } + val allLists = + library.allListNames.associateWith { emptyList() } val filledLists = allLists + listSubset diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index d576987c..00dee9b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -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 { val COLUMN_WATCH_NEXT_ID_INDEX = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 0ac9a6a2..074fcfcc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -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? { diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 0fa35e3b..b108ebbb 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -7,6 +7,17 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + android:orientation="horizontal" + app:layout_scrollFlags="scroll|enterAlways"> + @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14219d40..c935809b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,4 +627,5 @@ Alphabetical (Z to A) Select Library Open with + Looks like your library is empty :(\nLogin to a library account or add shows to your local library