From 67fe6730e02c846e9ef1e83308e3d20ace62524e Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:44:12 +0100 Subject: [PATCH 01/29] library work in progress --- .../lagradost/cloudstream3/MainActivity.kt | 1 + .../cloudstream3/syncproviders/SyncAPI.kt | 3 + .../cloudstream3/syncproviders/SyncRepo.kt | 15 +- .../syncproviders/providers/AniListApi.kt | 26 ++- .../syncproviders/providers/MALApi.kt | 23 ++- .../ui/library/LibraryFragment.kt | 106 ++++++++++++ .../ui/library/LibraryViewModel.kt | 74 ++++++++ .../cloudstream3/ui/library/PageAdapter.kt | 58 +++++++ .../ui/library/ViewpagerAdapter.kt | 85 ++++++++++ .../lagradost/cloudstream3/ui/library/test.kt | 11 ++ .../lagradost/cloudstream3/utils/AppUtils.kt | 46 +++++ app/src/main/res/color/item_select_color.xml | 2 + .../ic_baseline_collections_bookmark_24.xml | 6 + .../main/res/drawable/ic_baseline_sort_24.xml | 5 + .../main/res/drawable/ic_baseline_star_24.xml | 4 +- .../res/drawable/indicator_background.xml | 6 + app/src/main/res/drawable/rating_bg_color.xml | 6 + app/src/main/res/layout/fragment_library.xml | 63 +++++++ .../res/layout/library_viewpager_page.xml | 17 ++ .../layout/search_result_grid_expanded.xml | 159 ++++++++++-------- app/src/main/res/menu/bottom_nav_menu.xml | 28 +-- app/src/main/res/menu/library_menu.xml | 17 ++ .../main/res/navigation/mobile_navigation.xml | 9 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 7 + app/src/main/res/values/styles.xml | 4 + 26 files changed, 692 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt create mode 100644 app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_sort_24.xml create mode 100644 app/src/main/res/drawable/indicator_background.xml create mode 100644 app/src/main/res/drawable/rating_bg_color.xml create mode 100644 app/src/main/res/layout/fragment_library.xml create mode 100644 app/src/main/res/layout/library_viewpager_page.xml create mode 100644 app/src/main/res/menu/library_menu.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 6c9fadd8..9937f183 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -177,6 +177,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val isNavVisible = listOf( R.id.navigation_home, R.id.navigation_search, + R.id.navigation_library, R.id.navigation_downloads, R.id.navigation_settings, R.id.navigation_download_child, 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 5aa56a02..ddb9f660 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.ui.library.LibraryItem interface SyncAPI : OAuth2API { val mainUrl: String @@ -22,6 +23,8 @@ interface SyncAPI : OAuth2API { suspend fun search(name: String): List? + suspend fun getPersonalLibrary(): List? + fun getIdFromUrl(url : String) : String data class SyncSearchResult( 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 b621e81a..04e15f55 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -4,6 +4,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.ui.library.LibraryItem class SyncRepo(private val repo: SyncAPI) { val idPrefix = repo.idPrefix @@ -16,21 +17,25 @@ class SyncRepo(private val repo: SyncAPI) { return safeApiCall { repo.score(id, status) } } - suspend fun getStatus(id : String) : Resource { + suspend fun getStatus(id: String): Resource { return safeApiCall { repo.getStatus(id) ?: throw ErrorLoadingException("No data") } } - suspend fun getResult(id : String) : Resource { + suspend fun getResult(id: String): Resource { return safeApiCall { repo.getResult(id) ?: throw ErrorLoadingException("No data") } } - suspend fun search(query : String) : Resource> { + suspend fun search(query: String): Resource> { return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() } } - fun hasAccount() : Boolean { + suspend fun getPersonalLibrary(): Resource> { + return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() } + } + + fun hasAccount(): Boolean { return normalSafeApiCall { repo.loginInfo() != null } ?: false } - fun getIdFromUrl(url : String) : String = repo.getIdFromUrl(url) + fun getIdFromUrl(url: String): String = repo.getIdFromUrl(url) } \ No newline at end of file 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 606fee97..b4f0f790 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 @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.ui.library.LibraryItem import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -595,7 +596,26 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("score") val score: Int, @JsonProperty("private") val private: Boolean, @JsonProperty("media") val media: Media - ) + ) { + fun toLibraryItem(listName: String?): LibraryItem? { + return LibraryItem( + // English title first + this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull() + ?: "", + this.media.id.toString(), + listName ?: return null, + this.progress, + this.media.episodes, + this.score, + "AniList", + TvType.Anime, + this.media.coverImage.large ?: this.media.coverImage.medium, + null, + null, + null + ) + } + } data class Lists( @JsonProperty("status") val status: String?, @@ -630,6 +650,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } + override suspend fun getPersonalLibrary(): List? { + return getAnilistAnimeListSmart()?.map { it.entries.mapNotNull { entry -> entry.toLibraryItem(entry.status ?: it.status) } }?.flatten() + } + private suspend fun getFullAnilistList(): FullAnilistList? { var userID: Int? = null /** WARNING ASSUMES ONE USER! **/ 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 ea27720a..d9011a1b 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 @@ -7,11 +7,13 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ShowStatus +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.ui.library.LibraryItem import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject @@ -381,7 +383,22 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { data class Data( @JsonProperty("node") val node: Node, @JsonProperty("list_status") val list_status: ListStatus?, - ) + ) { + fun toLibraryItem(): LibraryItem { + return LibraryItem( + this.node.title, + this.node.id.toString(), + this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE", + this.list_status?.num_episodes_watched, + this.node.num_episodes, + this.list_status?.score, + "MAL", + TvType.Anime, + this.node.main_picture?.large ?: this.node.main_picture?.medium, + null, null, null + ) + } + } data class Paging( @JsonProperty("next") val next: String? @@ -424,6 +441,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } } + override suspend fun getPersonalLibrary(): List? { + return getMalAnimeListSmart()?.map { it.toLibraryItem() } + } + private suspend fun getMalAnimeList(): Array { checkMalToken() var offset = 0 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 new file mode 100644 index 00000000..932d3916 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -0,0 +1,106 @@ +package com.lagradost.cloudstream3.ui.library + +import android.content.Context +import androidx.lifecycle.ViewModelProvider +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.activityViewModels +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.syncproviders.AccountManager +import com.lagradost.cloudstream3.syncproviders.providers.MALApi +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar +import kotlinx.android.synthetic.main.fragment_library.* +import kotlinx.android.synthetic.main.library_viewpager_page.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +class LibraryFragment : Fragment() { + + companion object { + fun newInstance() = LibraryFragment() + } + + private val libraryViewModel: LibraryViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_library, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + context?.fixPaddingStatusbar(library_root) + + val sortView = + menu_toolbar?.menu?.findItem(R.id.sort_button) + sortView?.setOnMenuItemClickListener { + val methods = libraryViewModel.sortingMethods + .map { txt(it.stringRes).asString(context ?: view.context) } + + activity?.showBottomDialog( + methods, + libraryViewModel.sortingMethods.indexOf(libraryViewModel.currentSortingMethod), + "Sort by", + false, + {}, + { + val method = libraryViewModel.sortingMethods[it] + libraryViewModel.sort(method) + } + ) + true + } + + val searchView = + menu_toolbar?.menu?.findItem(R.id.search_button)?.actionView as? MenuSearchView + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + libraryViewModel.sort(ListSorting.Query, newText) + return true + } + }) + + libraryViewModel.loadPages() + + viewpager?.setPageTransformer(HomeScrollTransformer()) + viewpager?.adapter = viewpager.adapter ?: ViewpagerAdapter(emptyList()) + viewpager?.offscreenPageLimit = 10 + + observe(libraryViewModel.pages) { pages -> + (viewpager.adapter as? ViewpagerAdapter)?.pages = pages + viewpager.adapter?.notifyItemChanged(viewpager?.currentItem ?: 0) + + TabLayoutMediator( + library_tab_layout, + viewpager, + ) { tab, position -> + tab.text = pages.getOrNull(position)?.title + }.attach() + } + } +} + +class MenuSearchView(context: Context) : SearchView(context) { + override fun onActionViewCollapsed() { + super.onActionViewCollapsed() + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..167290e1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -0,0 +1,74 @@ +package com.lagradost.cloudstream3.ui.library + +import androidx.annotation.StringRes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import me.xdrop.fuzzywuzzy.FuzzySearch + +enum class ListSorting(@StringRes val stringRes: Int) { + Query(R.string.none), + RatingHigh(R.string.sort_rating_desc), + RatingLow(R.string.sort_rating_asc), + UpdatedNew(R.string.sort_updated_new), + UpdatedOld(R.string.sort_updated_old), + AlphabeticalA(R.string.sort_alphabetical_a), + AlphabeticalZ(R.string.sort_alphabetical_z), +} + +class LibraryViewModel : ViewModel() { + private val _pages: MutableLiveData> = MutableLiveData(emptyList()) + val pages: LiveData> = _pages + + private val _currentApiName: MutableLiveData = MutableLiveData("") + val currentApiName: LiveData = _currentApiName + + private val listApis = SyncApis.filter { it.hasAccount() } + private var currentApi = listApis.firstOrNull() + + val sortingMethods = listOf( + ListSorting.RatingHigh, + ListSorting.RatingLow, +// ListSorting.UpdatedNew, +// ListSorting.UpdatedOld, + ListSorting.AlphabeticalA, + ListSorting.AlphabeticalZ, + ) + + var currentSortingMethod: ListSorting = sortingMethods.first() + private set + + fun switchList() { + currentApi = listApis[(listApis.indexOf(currentApi) + 1) % listApis.size] + loadPages() + } + + fun sort(method: ListSorting, query: String? = null) { + val currentList = pages.value ?: return + currentSortingMethod = method + currentList.forEachIndexed { index, page -> + page.sort(method, query) + } + _pages.postValue(currentList) + } + + fun loadPages() { + ioSafe { + currentApi?.let { repo -> + val list = (repo.getPersonalLibrary() as? Resource.Success)?.value + val pages = (list ?: emptyList()).groupBy { it.listName }.map { + Page( + it.key, + it.value + ) + } + _pages.postValue(pages) + _currentApiName.postValue(repo.name) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt new file mode 100644 index 00000000..7db40f88 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt @@ -0,0 +1,58 @@ +package com.lagradost.cloudstream3.ui.library + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.search.SearchResultBuilder +import com.lagradost.cloudstream3.utils.AppUtils +import kotlinx.android.synthetic.main.search_result_grid_expanded.view.* + +class PageAdapter( + override val items: MutableList, +) : + AppUtils.DiffAdapter(items) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return LibraryItemViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.search_result_grid_expanded, parent, false) + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is LibraryItemViewHolder -> { + holder.bind(items[position], position) + } + } + } + + inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(item: LibraryItem, position: Int) { + SearchResultBuilder.bind( + { println("CLICKED ${it.action}") }, + item, + position, + itemView, + ) + + // Set watch progress bar +// val showProgress = item.episodesCompleted != null && item.episodesTotal != null +// itemView.watchProgress.isVisible = showProgress +// +// if (showProgress) { +// itemView.watchProgress.max = item.episodesTotal!! +// itemView.watchProgress.progress = item.episodesCompleted!! +// } + itemView.imageText.text = item.name + val showRating = (item.personalRating ?: 0) != 0 + itemView.text_rating.isVisible = showRating + if (showRating) { + itemView.text_rating.text = item.personalRating.toString() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt new file mode 100644 index 00000000..3939f50e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -0,0 +1,85 @@ +package com.lagradost.cloudstream3.ui.library + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType +import kotlinx.android.synthetic.main.library_viewpager_page.view.* +import me.xdrop.fuzzywuzzy.FuzzySearch + +data class Page( + val title: String, var items: List +) { + 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, + 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?, + override var id: Int?, + override var quality: SearchQuality?, +) : SearchResponse + + +class ViewpagerAdapter(var pages: List) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return PageViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.library_viewpager_page, parent, false) + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is PageViewHolder -> { + holder.bind(pages[position]) + } + } + } + + inner class PageViewHolder(private val itemViewTest: View) : + RecyclerView.ViewHolder(itemViewTest) { + fun bind(page: Page) { + if (itemViewTest.page_recyclerview?.adapter == null) { + itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList()) + itemView.page_recyclerview?.spanCount = 4 + } else { + (itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items) + itemViewTest.page_recyclerview?.scrollToPosition(0) + } + } + } + + override fun getItemCount(): Int { + return pages.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt new file mode 100644 index 00000000..9eabac47 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt @@ -0,0 +1,11 @@ +package com.lagradost.cloudstream3.ui.library + +import android.view.View +import androidx.viewpager2.widget.ViewPager2 + +class HomeScrollTransformer : ViewPager2.PageTransformer { + + override fun transformPage(view: View, position: Float) { + } +} + 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 1c7bb214..2e9e0c88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -32,6 +32,7 @@ import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.tvprovider.media.tv.PreviewChannelHelper @@ -64,6 +65,7 @@ import okhttp3.Cache import java.io.* import java.net.URL import java.net.URLDecoder +import kotlin.system.measureTimeMillis object AppUtils { fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) { @@ -263,6 +265,50 @@ object AppUtils { } } + abstract class DiffAdapter( + open val items: MutableList, + val comparison: (first: T, second: T) -> Boolean = { first, second -> + first.hashCode() == second.hashCode() + } + ) : + RecyclerView.Adapter() { + override fun getItemCount(): Int { + return items.size + } + + fun updateList(newList: List) { + val time = measureTimeMillis { + + val diffResult = DiffUtil.calculateDiff( + GenericDiffCallback(this.items, newList) + ) + + items.clear() + items.addAll(newList) + + diffResult.dispatchUpdatesTo(this) + } + println("TIME TAKEn $time") + } + + inner class GenericDiffCallback( + private val oldList: List, + private val newList: List + ) : + DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + comparison(oldList[oldItemPosition], newList[newItemPosition]) + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + } + } + + fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) { runOnUiThread { val context = this diff --git a/app/src/main/res/color/item_select_color.xml b/app/src/main/res/color/item_select_color.xml index 0d2834dd..3d69c540 100644 --- a/app/src/main/res/color/item_select_color.xml +++ b/app/src/main/res/color/item_select_color.xml @@ -1,5 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml b/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml new file mode 100644 index 00000000..fc90e300 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_baseline_sort_24.xml b/app/src/main/res/drawable/ic_baseline_sort_24.xml new file mode 100644 index 00000000..96d46231 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_sort_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_star_24.xml b/app/src/main/res/drawable/ic_baseline_star_24.xml index ab099425..2dcadb7f 100644 --- a/app/src/main/res/drawable/ic_baseline_star_24.xml +++ b/app/src/main/res/drawable/ic_baseline_star_24.xml @@ -1,5 +1,5 @@ - + android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/drawable/indicator_background.xml b/app/src/main/res/drawable/indicator_background.xml new file mode 100644 index 00000000..ef44fb7c --- /dev/null +++ b/app/src/main/res/drawable/indicator_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/rating_bg_color.xml b/app/src/main/res/drawable/rating_bg_color.xml new file mode 100644 index 00000000..60e62bab --- /dev/null +++ b/app/src/main/res/drawable/rating_bg_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml new file mode 100644 index 00000000..9c5cbf04 --- /dev/null +++ b/app/src/main/res/layout/fragment_library.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/library_viewpager_page.xml b/app/src/main/res/layout/library_viewpager_page.xml new file mode 100644 index 00000000..940b5a69 --- /dev/null +++ b/app/src/main/res/layout/library_viewpager_page.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/layout/search_result_grid_expanded.xml b/app/src/main/res/layout/search_result_grid_expanded.xml index 710c6cf8..afa75393 100644 --- a/app/src/main/res/layout/search_result_grid_expanded.xml +++ b/app/src/main/res/layout/search_result_grid_expanded.xml @@ -1,90 +1,111 @@ - + + android:id="@+id/background_card" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_margin="2dp" + android:layout_marginBottom="2dp" + android:elevation="10dp" + app:cardBackgroundColor="?attr/primaryGrayBackground" + app:cardCornerRadius="@dimen/rounded_image_radius" + app:layout_constraintBottom_toTopOf="@+id/imageText" + app:layout_constraintDimensionRatio="0.6333:1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:layout_height="match_parent" + android:contentDescription="@string/search_poster_img_des" + android:duplicateParentState="true" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:scaleType="centerCrop" + tools:src="@drawable/example_poster" /> + android:id="@+id/text_quality" + style="@style/SearchBox" + android:background="@drawable/type_bg_color" + android:textColor="@color/textColor" + tools:text="@string/quality_hd" /> + + + android:background="@drawable/dub_bg_color" + android:text="@string/app_dubbed_text" /> + android:id="@+id/text_is_sub" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/sub_bg_color" + android:text="@string/app_subbed_text" /> + android:id="@+id/text_flag" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@color/transparent" + android:textSize="20sp" + android:visibility="gone" + tools:text="πŸ‡ΈπŸ‡ͺ" + tools:visibility="visible" /> + android:id="@+id/text_rating" + android:visibility="gone" + tools:visibility="visible" + tools:text="7.7" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/rating_bg_color" + android:text="@string/app_subbed_text" + app:drawableStartCompat="@drawable/ic_baseline_star_24" /> + + - \ No newline at end of file + android:id="@+id/imageText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:layout_weight="0" + android:ellipsize="end" + android:gravity="center" + android:maxLines="2" + android:minLines="2" + android:paddingStart="5dp" + android:paddingTop="5dp" + android:paddingEnd="5dp" + android:paddingBottom="5dp" + android:textColor="?attr/textColor" + android:textSize="13sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="The Perfect Run\nThe Perfect Run" /> + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index e3fd3d22..ba171654 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -2,19 +2,23 @@ + android:id="@+id/navigation_home" + android:icon="@drawable/ic_outline_home_24" + android:title="@string/title_home" /> + android:id="@+id/navigation_search" + android:icon="@drawable/search_icon" + android:title="@string/title_search" /> + android:id="@+id/navigation_library" + android:icon="@drawable/ic_baseline_collections_bookmark_24" + android:title="@string/library" /> + android:id="@+id/navigation_downloads" + android:icon="@drawable/netflix_download" + android:title="@string/title_downloads" /> + \ No newline at end of file diff --git a/app/src/main/res/menu/library_menu.xml b/app/src/main/res/menu/library_menu.xml new file mode 100644 index 00000000..f21d998d --- /dev/null +++ b/app/src/main/res/menu/library_menu.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 3c45ee70..0afca83b 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -144,6 +144,15 @@ app:popEnterAnim="@anim/enter_anim" app:popExitAnim="@anim/exit_anim" /> + + #F53B66 #3BF585 ?attr/colorPrimaryDark + #3F51B5 #FF6F63 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 11c90d58..e1af1e0e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -253,6 +253,7 @@ Error backing up %s Search + Library Accounts Updates and backup @@ -615,4 +616,10 @@ Safe Mode enabled An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble. View crash info + Rating (High to Low) + Rating (Low to High) + Updated (New to Old) + Updated (Old to New) + Alphabetical (A to Z) + Alphabetical (Z to A) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2fb9b5b4..f758213f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -299,6 +299,10 @@ false + + From 05dc032df6be9f35b0dfa220b392350dceb88b88 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sun, 20 Nov 2022 17:05:11 +0100 Subject: [PATCH 02/29] library fixes --- .../ui/library/LibraryFragment.kt | 34 ++-- .../ui/library/LibraryScrollTransformer.kt | 16 ++ .../ui/library/ViewpagerAdapter.kt | 27 +++- .../lagradost/cloudstream3/ui/library/test.kt | 11 -- .../lagradost/cloudstream3/utils/UIHelper.kt | 2 +- .../drawable/ic_outline_account_circle_24.xml | 6 + app/src/main/res/layout/fragment_library.xml | 149 ++++++++++++------ .../res/layout/library_viewpager_page.xml | 16 +- app/src/main/res/menu/bottom_nav_menu.xml | 2 +- 9 files changed, 165 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt create mode 100644 app/src/main/res/drawable/ic_outline_account_circle_24.xml 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 932d3916..8a1a7e1f 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 @@ -1,31 +1,20 @@ package com.lagradost.cloudstream3.ui.library import android.content.Context -import androidx.lifecycle.ViewModelProvider import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.appcompat.widget.SearchView import androidx.fragment.app.activityViewModels -import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.syncproviders.AccountManager -import com.lagradost.cloudstream3.syncproviders.providers.MALApi import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import kotlinx.android.synthetic.main.fragment_library.* -import kotlinx.android.synthetic.main.library_viewpager_page.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock class LibraryFragment : Fragment() { @@ -46,9 +35,7 @@ class LibraryFragment : Fragment() { super.onViewCreated(view, savedInstanceState) context?.fixPaddingStatusbar(library_root) - val sortView = - menu_toolbar?.menu?.findItem(R.id.sort_button) - sortView?.setOnMenuItemClickListener { + sort_fab?.setOnClickListener { val methods = libraryViewModel.sortingMethods .map { txt(it.stringRes).asString(context ?: view.context) } @@ -63,13 +50,11 @@ class LibraryFragment : Fragment() { libraryViewModel.sort(method) } ) - true } - val searchView = - menu_toolbar?.menu?.findItem(R.id.search_button)?.actionView as? MenuSearchView - searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + main_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { + libraryViewModel.sort(ListSorting.Query, query) return true } @@ -81,9 +66,16 @@ class LibraryFragment : Fragment() { libraryViewModel.loadPages() - viewpager?.setPageTransformer(HomeScrollTransformer()) - viewpager?.adapter = viewpager.adapter ?: ViewpagerAdapter(emptyList()) - viewpager?.offscreenPageLimit = 10 + viewpager?.setPageTransformer(LibraryScrollTransformer()) + viewpager?.adapter = + viewpager.adapter ?: ViewpagerAdapter(emptyList()) { isScrollingDown: Boolean -> + if (isScrollingDown) { + sort_fab?.shrink() + } else { + sort_fab?.extend() + } + } + viewpager?.offscreenPageLimit = 2 observe(libraryViewModel.pages) { pages -> (viewpager.adapter as? ViewpagerAdapter)?.pages = pages diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt new file mode 100644 index 00000000..b954cc26 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt @@ -0,0 +1,16 @@ +package com.lagradost.cloudstream3.ui.library + +import android.view.View +import androidx.viewpager2.widget.ViewPager2 +import kotlinx.android.synthetic.main.library_viewpager_page.view.* + +class LibraryScrollTransformer : ViewPager2.PageTransformer { + override fun transformPage(page: View, position: Float) { + val padding = (-position * page.width).toInt() + page.page_recyclerview.setPadding( + padding, 0, + -padding, 0 + ) + } +} + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 3939f50e..eea26351 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -1,13 +1,18 @@ package com.lagradost.cloudstream3.ui.library +import android.os.Build import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.widget.NestedScrollView import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.OnFlingListener +import androidx.recyclerview.widget.RecyclerView.OnScrollListener import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import kotlinx.android.synthetic.main.library_viewpager_page.view.* import me.xdrop.fuzzywuzzy.FuzzySearch @@ -50,7 +55,7 @@ data class LibraryItem( ) : SearchResponse -class ViewpagerAdapter(var pages: List) : RecyclerView.Adapter() { +class ViewpagerAdapter(var pages: List, val scrollCallback: (isScrollingDown: Boolean) -> Unit) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return PageViewHolder( LayoutInflater.from(parent.context) @@ -71,11 +76,29 @@ class ViewpagerAdapter(var pages: List) : RecyclerView.Adapter= Build.VERSION_CODES.M) { + itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> + println("DOWN ${(scrollY - oldScrollY)}") + val diff = scrollY - oldScrollY + if (diff == 0) return@setOnScrollChangeListener + + scrollCallback.invoke(diff > 0) + } + } else { + itemViewTest.page_recyclerview.onFlingListener = object : OnFlingListener() { + override fun onFling(velocityX: Int, velocityY: Int): Boolean { + scrollCallback.invoke(velocityY > 0) + return false + } + } + } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt deleted file mode 100644 index 9eabac47..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/test.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.view.View -import androidx.viewpager2.widget.ViewPager2 - -class HomeScrollTransformer : ViewPager2.PageTransformer { - - override fun transformPage(view: View, position: Float) { - } -} - diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index ab49492a..5e65429d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -102,7 +102,7 @@ object UIHelper { listView.requestLayout() } - fun Activity?.getSpanCount(): Int? { + fun Context?.getSpanCount(): Int? { val compactView = false val spanCountLandscape = if (compactView) 2 else 6 val spanCountPortrait = if (compactView) 1 else 3 diff --git a/app/src/main/res/drawable/ic_outline_account_circle_24.xml b/app/src/main/res/drawable/ic_outline_account_circle_24.xml new file mode 100644 index 00000000..cc564471 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_account_circle_24.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 9c5cbf04..ad695d6d 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -1,63 +1,110 @@ - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + 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" + app:tabIndicatorGravity="center" + app:tabIndicatorHeight="30dp" + app:tabMode="scrollable" + app:tabSelectedTextColor="@color/lightTextColor" + app:tabTextAppearance="@style/TabNoCaps" + app:tabTextColor="@color/textColor" /> + - - \ No newline at end of file + diff --git a/app/src/main/res/layout/library_viewpager_page.xml b/app/src/main/res/layout/library_viewpager_page.xml index 940b5a69..f69f68b5 100644 --- a/app/src/main/res/layout/library_viewpager_page.xml +++ b/app/src/main/res/layout/library_viewpager_page.xml @@ -1,17 +1,11 @@ - - - - + android:clipToPadding="false" + tools:listitem="@layout/home_result_grid_expanded" /> diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index ba171654..38e17675 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -11,7 +11,7 @@ android:title="@string/title_search" /> Date: Mon, 5 Dec 2022 20:49:45 +0100 Subject: [PATCH 03/29] backend fixes for opening from sync urls --- .../com/lagradost/cloudstream3/MainAPI.kt | 28 +++++++-- .../metaproviders/AnilistRedirector.kt | 30 ---------- .../metaproviders/SyncRedirector.kt | 57 +++++++++++++++++++ .../syncproviders/providers/AniListApi.kt | 2 +- .../syncproviders/providers/MALApi.kt | 2 +- .../ui/library/LibraryFragment.kt | 9 ++- .../cloudstream3/ui/library/PageAdapter.kt | 4 +- .../ui/library/ViewpagerAdapter.kt | 12 +++- .../ui/result/ResultViewModel2.kt | 21 ++++--- .../lagradost/cloudstream3/utils/SyncUtil.kt | 27 +++++---- app/src/main/res/values/styles.xml | 1 + 11 files changed, 134 insertions(+), 59 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index dc6cc454..f703e8ad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -10,15 +10,14 @@ 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.metaproviders.SyncIdName import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.loadExtractor import okhttp3.Interceptor import java.text.SimpleDateFormat import java.util.* @@ -402,6 +401,20 @@ abstract class MainAPI { open val hasMainPage = false open val hasQuickSearch = false + /** + * A set of which ids the provider can open with getLoadUrl() + * If the set contains SyncIdName.Imdb then getLoadUrl() can be started with + * an Imdb class which inherits from SyncId. + * + * getLoadUrl() is then used to get page url based on that ID. + * + * Example: + * "tt6723592" -> getLoadUrl(ImdbSyncId("tt6723592")) -> "mainUrl/imdb/tt6723592" -> load("mainUrl/imdb/tt6723592") + * + * This is used to launch pages from personal lists or recommendations using IDs. + **/ + open val supportedSyncNames = setOf() + open val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, @@ -412,7 +425,6 @@ abstract class MainAPI { open val vpnStatus = VPNStatus.None open val providerType = ProviderType.DirectProvider - open val mainPage = listOf(MainPageData("", "")) @WorkerThread @@ -471,6 +483,14 @@ abstract class MainAPI { open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? { return null } + + /** + * Get the load() url based on a sync ID like IMDb or MAL. + * Only contains SyncIds based on supportedSyncUrls. + **/ + open suspend fun getLoadUrl(name: SyncIdName, id: String): String? { + return null + } } /** Might need a different implementation for desktop*/ diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt deleted file mode 100644 index 208db14b..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.lagradost.cloudstream3.metaproviders - -import com.lagradost.cloudstream3.ErrorLoadingException -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi -import com.lagradost.cloudstream3.utils.SyncUtil - -object SyncRedirector { - val syncApis = SyncApis - - suspend fun redirect(url: String, preferredUrl: String): String { - for (api in syncApis) { - if (url.contains(api.mainUrl)) { - val otherApi = when (api.name) { - aniListApi.name -> "anilist" - malApi.name -> "myanimelist" - else -> return url - } - - return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl -> - realUrl.contains(preferredUrl) - } ?: run { - throw ErrorLoadingException("Page does not exist on $preferredUrl") - } - } - } - return url - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt new file mode 100644 index 00000000..41cf413c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt @@ -0,0 +1,57 @@ +package com.lagradost.cloudstream3.metaproviders + +import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi +import com.lagradost.cloudstream3.utils.SyncUtil + +enum class SyncIdName { + AniList, + MyAnimeList, + Trakt, + Imdb +} + +object SyncRedirector { + val syncApis = SyncApis + private val syncIds = + listOf( + SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""), + SyncIdName.AniList to Regex("""anilist\.co\/anime\/(\d+)""") + ) + + suspend fun redirect(url: String, providerApi: MainAPI): String { + // Tries built in ID -> ProviderUrl + for (api in syncApis) { + if (url.contains(api.mainUrl)) { + val otherApi = when (api.name) { + aniListApi.name -> "anilist" + malApi.name -> "myanimelist" + else -> return url + } + + SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl -> + realUrl.contains(providerApi.mainUrl) + }?.let { + return it + } +// ?: run { +// throw ErrorLoadingException("Page does not exist on $preferredUrl") +// } + } + } + + // Tries provider solution + return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) -> + if (providerApi.supportedSyncNames.contains(syncName)) { + syncRegex.find(url)?.value?.let { + suspendSafeApiCall { + providerApi.getLoadUrl(syncName, it) + } + } + } else null + } ?: url + } +} \ No newline at end of file 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 b4f0f790..6976bfc5 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 @@ -602,7 +602,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { // English title first this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull() ?: "", - this.media.id.toString(), + "https://anilist.co/anime/${this.media.id}/", listName ?: return null, this.progress, this.media.episodes, 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 d9011a1b..8f734856 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 @@ -387,7 +387,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { fun toLibraryItem(): LibraryItem { return LibraryItem( this.node.title, - this.node.id.toString(), + "https://myanimelist.net/anime/${this.node.id}/", this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE", this.list_status?.num_episodes_watched, this.node.num_episodes, 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 8a1a7e1f..9bb412b7 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 @@ -12,6 +12,8 @@ import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD +import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import kotlinx.android.synthetic.main.fragment_library.* @@ -68,12 +70,17 @@ class LibraryFragment : Fragment() { viewpager?.setPageTransformer(LibraryScrollTransformer()) viewpager?.adapter = - viewpager.adapter ?: ViewpagerAdapter(emptyList()) { isScrollingDown: Boolean -> + viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean -> if (isScrollingDown) { sort_fab?.shrink() } else { sort_fab?.extend() } + }) { searchClickCallback -> + println("SEARCH CLICK $searchClickCallback") + if (searchClickCallback.action == SEARCH_ACTION_LOAD) { + activity?.loadSearchResult(searchClickCallback.card) + } } viewpager?.offscreenPageLimit = 2 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt index 7db40f88..4bac94e2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt @@ -6,12 +6,14 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.utils.AppUtils import kotlinx.android.synthetic.main.search_result_grid_expanded.view.* class PageAdapter( override val items: MutableList, + val clickCallback: (SearchClickCallback) -> Unit ) : AppUtils.DiffAdapter(items) { @@ -33,7 +35,7 @@ class PageAdapter( inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(item: LibraryItem, position: Int) { SearchResultBuilder.bind( - { println("CLICKED ${it.action}") }, + this@PageAdapter.clickCallback, item, position, itemView, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index eea26351..fbb3ec1e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import kotlinx.android.synthetic.main.library_viewpager_page.view.* import me.xdrop.fuzzywuzzy.FuzzySearch @@ -55,7 +56,11 @@ data class LibraryItem( ) : SearchResponse -class ViewpagerAdapter(var pages: List, val scrollCallback: (isScrollingDown: Boolean) -> Unit) : RecyclerView.Adapter() { +class ViewpagerAdapter( + var pages: List, + val scrollCallback: (isScrollingDown: Boolean) -> Unit, + val clickCallback: (SearchClickCallback) -> Unit +) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return PageViewHolder( LayoutInflater.from(parent.context) @@ -75,8 +80,9 @@ class ViewpagerAdapter(var pages: List, val scrollCallback: (isScrollingDo RecyclerView.ViewHolder(itemViewTest) { fun bind(page: Page) { if (itemViewTest.page_recyclerview?.adapter == null) { - itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList()) - itemView.page_recyclerview?.spanCount = this@PageViewHolder.itemView.context.getSpanCount() ?: 3 + itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList(), clickCallback) + itemView.page_recyclerview?.spanCount = + this@PageViewHolder.itemView.context.getSpanCount() ?: 3 } else { (itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items) itemViewTest.page_recyclerview?.scrollToPosition(0) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index b7e36f21..582ad4bd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -14,6 +14,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.CommonActivity.getCastSession @@ -1264,12 +1265,18 @@ class ResultViewModel2 : ViewModel() { val realRecommendations = ArrayList() // TODO: fix - //val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name) - // meta.recommendations?.forEach { rec -> - // apiNames.forEach { name -> - // realRecommendations.add(rec.copy(apiName = name)) - // } - // } + val apiNames = apis.filter { + it.name.contains("gogoanime", true) || + it.name.contains("9anime", true) + }.map { + it.name + } + + meta.recommendations?.forEach { rec -> + apiNames.forEach { name -> + realRecommendations.add(rec.copy(apiName = name)) + } + } recommendations = recommendations?.union(realRecommendations)?.toList() ?: realRecommendations @@ -1913,7 +1920,7 @@ class ResultViewModel2 : ViewModel() { val validUrlResource = safeApiCall { SyncRedirector.redirect( url, - api.mainUrl + api ) } // TODO: fix diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index 7dda3e18..e5f2f2dc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -4,6 +4,7 @@ package com.lagradost.cloudstream3.utils import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.APIHolder.apis //import com.lagradost.cloudstream3.animeproviders.AniflixProvider import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError @@ -78,17 +79,21 @@ object SyncUtil { return null } - suspend fun getUrlsFromId(id: String, type: String = "anilist") : List { - return arrayListOf() - // val url = - // "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json" - // val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed() - // val pages = response.pages ?: return emptyList() - // val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList() - // if(type == "anilist") { // TODO MAKE BETTER - // current.add("${AniflixProvider().mainUrl}/anime/$id") - // } - // return current + suspend fun getUrlsFromId(id: String, type: String = "anilist"): List { + val url = + "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json" + val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed() + val pages = response.pages ?: return emptyList() + val current = + pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values) + .mapNotNull { it.url }.toMutableList() + + if (type == "anilist") { // TODO MAKE BETTER + apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach { + current.add("${it.mainUrl}/anime/$id") + } + } + return current } data class SyncPage( diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f758213f..517300ea 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -301,6 +301,7 @@ + + + + From 26320bb535b6cb4aa2789e3ee40a16f501ead02b Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Wed, 25 Jan 2023 19:20:53 +0100 Subject: [PATCH 19/29] Reworked backend to make list titles use UiText --- .../cloudstream3/syncproviders/SyncAPI.kt | 15 ++-- .../syncproviders/providers/AniListApi.kt | 47 ++++++------ .../syncproviders/providers/LocalList.kt | 22 +++--- .../syncproviders/providers/MALApi.kt | 72 +++++++++++-------- .../ui/library/LibraryFragment.kt | 2 +- .../ui/library/LibraryViewModel.kt | 13 ++-- .../cloudstream3/ui/library/PageAdapter.kt | 19 +++-- .../cloudstream3/utils/DataStoreHelper.kt | 3 +- .../layout/search_result_grid_expanded.xml | 2 +- 9 files changed, 106 insertions(+), 89 deletions(-) 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 ab9db4ea..71ea374d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.ui.library.ListSorting +import com.lagradost.cloudstream3.ui.result.UiText import me.xdrop.fuzzywuzzy.FuzzySearch enum class SyncIdName { @@ -101,7 +102,7 @@ interface SyncAPI : OAuth2API { data class Page( - val title: String, var items: List + val title: UiText, var items: List ) { fun sort(method: ListSorting?, query: String? = null) { items = when (method) { @@ -123,11 +124,12 @@ interface SyncAPI : OAuth2API { } 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, - /** Not necessarily sorted list of all library items, will be grouped by listName */ - val allLibraryItems: List + val allLibraryLists: List + ) + + data class LibraryList( + val name: UiText, + val items: List ) data class LibraryItem( @@ -135,7 +137,6 @@ interface SyncAPI : OAuth2API { 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 */ 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 6b3c408d..ed0c3f78 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 @@ -1,9 +1,9 @@ package com.lagradost.cloudstream3.syncproviders.providers +import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.capitalize import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName +import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -316,14 +317,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } // Changing names of these will show up in UI - enum class AniListStatusType(var value: Int) { - Watching(0), - Completed(1), - Paused(2), - Dropped(3), - Planning(4), - ReWatching(5), - None(-1) + enum class AniListStatusType(var value: Int, @StringRes val stringRes: Int) { + Watching(0, R.string.type_watching), + Completed(1, R.string.type_completed), + Paused(2, R.string.type_on_hold), + Dropped(3, R.string.type_dropped), + Planning(4, R.string.type_plan_to_watch), + ReWatching(5, R.string.type_re_watching), + None(-1, R.string.none) } fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp } @@ -339,7 +340,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - fun convertAnilistStringToStatus(string: String): AniListStatusType { + fun convertAniListStringToStatus(string: String): AniListStatusType { return fromIntToAnimeStatus(aniListStatusString.indexOf(string)) } @@ -609,7 +610,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("private") val private: Boolean, @JsonProperty("media") val media: Media ) { - fun toLibraryItem(listName: String?): SyncAPI.LibraryItem? { + fun toLibraryItem(): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( // English title first this.media.title.english ?: this.media.title.romaji @@ -617,7 +618,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { ?: "", "https://anilist.co/anime/${this.media.id}/", this.media.id.toString(), - listName?.lowercase()?.capitalize() ?: return null, this.progress, this.media.episodes, this.score, @@ -665,15 +665,20 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata { + val list = getAniListAnimeListSmart()?.groupBy { + convertAniListStringToStatus(it.status ?: "").stringRes + }?.mapValues { group -> + group.value.map { it.entries.map { entry -> entry.toLibraryItem() } }.flatten() + } ?: emptyMap() + + // To fill empty lists when AniList does not return them + val baseMap = + AniListStatusType.values().filter { it.value >= 0 }.associate { + it.stringRes to emptyList() + } + return SyncAPI.LibraryMetadata( - emptyList(), - getAniListAnimeListSmart()?.map { - it.entries.mapNotNull { entry -> - entry.toLibraryItem( - entry.status ?: it.status - ) - } - }?.flatten() ?: emptyList() + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } ) } @@ -695,7 +700,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { startedAt { year month day } updatedAt progress - score + score (format: POINT_100) private media { 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 7a53e6a4..44384c92 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 @@ -1,7 +1,6 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.fragment.app.FragmentActivity -import com.lagradost.cloudstream3.AcraApplication import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI @@ -68,20 +67,21 @@ class LocalList : SyncAPI { }?.distinctBy { it.first } ?: return null val list = ioWork { - watchStatusIds.mapNotNull { - getBookmarkedData(it.first)?.toLibraryItem(it.second) + watchStatusIds.groupBy { + it.second.stringRes + }.mapValues { group -> + group.value.mapNotNull { + getBookmarkedData(it.first)?.toLibraryItem() + } } } + val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { + // None is not something to display + it.stringRes to emptyList() + } 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 + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } ) } 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 7745f0d2..b91c9759 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 @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.syncproviders.providers import android.util.Base64 +import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AcraApplication.Companion.getKey @@ -15,6 +16,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName +import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject @@ -257,6 +259,32 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { const val MAL_UNIXTIME_KEY: String = "mal_unixtime" // When token expires const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api + + fun convertToStatus(string: String): MalStatusType { + return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) + } + + enum class MalStatusType(var value: Int, @StringRes val stringRes: Int) { + Watching(0, R.string.type_watching), + Completed(1, R.string.type_completed), + OnHold(2, R.string.type_on_hold), + Dropped(3, R.string.type_dropped), + PlanToWatch(4, R.string.type_plan_to_watch), + None(-1, R.string.type_none) + } + + private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp } + return when (inp) { + -1 -> MalStatusType.None + 0 -> MalStatusType.Watching + 1 -> MalStatusType.Completed + 2 -> MalStatusType.OnHold + 3 -> MalStatusType.Dropped + 4 -> MalStatusType.PlanToWatch + 5 -> MalStatusType.Watching + else -> MalStatusType.None + } + } } override suspend fun handleRedirect(url: String): Boolean { @@ -394,10 +422,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { this.node.title, "https://myanimelist.net/anime/${this.node.id}/", this.node.id.toString(), - this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE", this.list_status?.num_episodes_watched, this.node.num_episodes, - this.list_status?.score, + this.list_status?.score?.times(10), "MAL", TvType.Anime, this.node.main_picture?.large ?: this.node.main_picture?.medium, @@ -448,9 +475,20 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata { + val list = getMalAnimeListSmart()?.groupBy { + convertToStatus(it.list_status?.status ?: "").stringRes + }?.mapValues { group -> + group.value.map { it.toLibraryItem() } + } ?: emptyMap() + + // To fill empty lists when MAL does not return them + val baseMap = + MalStatusType.values().filter { it.value >= 0 }.associate { + it.stringRes to emptyList() + } + return SyncAPI.LibraryMetadata( - emptyList(), - getMalAnimeListSmart()?.map { it.toLibraryItem() } ?: emptyList() + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } ) } @@ -469,10 +507,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return fullList.toTypedArray() } - fun convertToStatus(string: String): MalStatusType { - return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) - } - private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? { val user = "@me" val auth = getAuth() ?: return null @@ -586,28 +620,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return user } - enum class MalStatusType(var value: Int) { - Watching(0), - Completed(1), - OnHold(2), - Dropped(3), - PlanToWatch(4), - None(-1) - } - - private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp } - return when (inp) { - -1 -> MalStatusType.None - 0 -> MalStatusType.Watching - 1 -> MalStatusType.Completed - 2 -> MalStatusType.OnHold - 3 -> MalStatusType.Dropped - 4 -> MalStatusType.PlanToWatch - 5 -> MalStatusType.Watching - else -> MalStatusType.None - } - } - private suspend fun setScoreRequest( id: Int, status: MalStatusType? = null, 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 87ab0821..baf8f71e 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 @@ -297,7 +297,7 @@ class LibraryFragment : Fragment() { library_tab_layout, viewpager, ) { tab, position -> - tab.text = pages.getOrNull(position)?.title + tab.text = pages.getOrNull(position)?.title?.asStringNull(context) }.attach() loading_indicator?.hide() } 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 51bfb16f..d01c699a 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 @@ -92,18 +92,13 @@ class LibraryViewModel : ViewModel() { repo.requireLibraryRefresh = false - val listSubset = library.allLibraryItems.groupBy { it.listName } - val allLists = - library.allListNames.associateWith { emptyList() } - - val filledLists = allLists + listSubset - - val pages = filledLists.map { + val pages = library.allLibraryLists.map { SyncAPI.Page( - it.key, - it.value + it.name, + it.items ) } + _pages.postValue(Resource.Success(pages)) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt index 0909d845..2435f8be 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt @@ -49,10 +49,10 @@ class PageAdapter( return ColorUtils.calculateLuminance(color) < 0.5 } - fun getDifferentColor(color: Int, ratio : Float = 0.7f) : Int { - return if(isDark(color)) { + fun getDifferentColor(color: Int, ratio: Float = 0.7f): Int { + return if (isDark(color)) { ColorUtils.blendARGB(color, Color.WHITE, ratio) - } else{ + } else { ColorUtils.blendARGB(color, Color.BLACK, ratio) } } @@ -74,7 +74,7 @@ class PageAdapter( itemView, colorCallback = { palette -> AcraApplication.context?.let { ctx -> - val defColor = ContextCompat.getColor(ctx,R.color.ratingColorBg) + val defColor = ContextCompat.getColor(ctx, R.color.ratingColorBg) var bg = palette.getDarkVibrantColor(defColor) if (bg == defColor) { bg = palette.getDarkMutedColor(defColor) @@ -83,11 +83,12 @@ class PageAdapter( bg = palette.getVibrantColor(defColor) } - val fg = getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor)) + val fg = + getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor)) itemView.text_rating.apply { setTextColor(ColorStateList.valueOf(fg)) } - itemView.text_rating_holder?.backgroundTintList =ColorStateList.valueOf(bg) + itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg) itemView.watchProgress?.apply { progressTintList = ColorStateList.valueOf(fg) progressBackgroundTintList = ColorStateList.valueOf(bg) @@ -118,7 +119,11 @@ class PageAdapter( val showRating = (item.personalRating ?: 0) != 0 itemView.text_rating_holder.isVisible = showRating if (showRating) { - itemView.text_rating.text = "β˜… ${item.personalRating.toString()}" + // We want to show 8.5 but not 8.0 hence the replace + val rating = ((item.personalRating ?: 0).toDouble() / 10).toString() + .replace(".0", "") + + itemView.text_rating.text = "β˜… $rating" } } } 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 074fcfcc..df6f209d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -55,12 +55,11 @@ object DataStoreHelper { @JsonProperty("quality") override var quality: SearchQuality? = null, @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, ) : SearchResponse { - fun toLibraryItem(state: WatchType): SyncAPI.LibraryItem { + fun toLibraryItem(): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( name, url, url, - state.name.lowercase().capitalize(), null, null, null, diff --git a/app/src/main/res/layout/search_result_grid_expanded.xml b/app/src/main/res/layout/search_result_grid_expanded.xml index 850191ba..d6deec2f 100644 --- a/app/src/main/res/layout/search_result_grid_expanded.xml +++ b/app/src/main/res/layout/search_result_grid_expanded.xml @@ -13,7 +13,7 @@ Date: Thu, 26 Jan 2023 18:09:22 +0100 Subject: [PATCH 20/29] Better notice when not logged in in library --- .../cloudstream3/ui/library/LibraryFragment.kt | 18 +++++++++++++++++- app/src/main/res/layout/fragment_library.xml | 1 - app/src/main/res/values/strings.xml | 3 ++- 3 files changed, 19 insertions(+), 3 deletions(-) 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 baf8f71e..42f0928d 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 @@ -253,6 +253,12 @@ class LibraryFragment : Fragment() { activity?.loadSearchResult( searchClickCallback.card ) + } else { + // Search when no provider can open + QuickSearchFragment.pushSearch( + activity, + searchClickCallback.card.name + ) } } LibraryOpenerType.None -> {} @@ -283,7 +289,16 @@ class LibraryFragment : Fragment() { when (resource) { is Resource.Success -> { val pages = resource.value - empty_list_textview?.isVisible = pages.all { it.items.isEmpty() } + val showNotice = pages.all { it.items.isEmpty() } + empty_list_textview?.isVisible = showNotice + if (showNotice) { + if (libraryViewModel.availableApiNames.size > 1) { + empty_list_textview?.setText(R.string.empty_library_logged_in_message) + } else { + empty_list_textview?.setText(R.string.empty_library_no_accounts_message) + } + } + (viewpager.adapter as? ViewpagerAdapter)?.pages = pages // Using notifyItemRangeChanged keeps the animations when sorting viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0) @@ -303,6 +318,7 @@ class LibraryFragment : Fragment() { } is Resource.Loading -> { loading_indicator?.show() + empty_list_textview?.isVisible = false } is Resource.Failure -> { // No user indication it failed :( diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index c86e5d93..116a4e03 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -14,7 +14,6 @@ android:layout_gravity="center" android:layout_margin="30dp" android:gravity="center" - android:text="@string/empty_library_message" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c935809b..acfc5932 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,5 +627,6 @@ 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 + Looks like your library is empty :(\nLogin to a library account or add shows to your local library + Looks like this list is empty, try switching to another one From 1f67644290aeb176e30b7aaf28e6265d02c6d1bc Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:54:24 +0100 Subject: [PATCH 21/29] Fix crash and sorting methods in library --- .../cloudstream3/syncproviders/SyncAPI.kt | 6 ++++- .../syncproviders/providers/AniListApi.kt | 12 +++++++++- .../syncproviders/providers/LocalList.kt | 11 +++++++++- .../syncproviders/providers/MALApi.kt | 22 ++++++++++++++++++- .../ui/library/LibraryViewModel.kt | 15 +++++-------- .../cloudstream3/ui/search/SearchAdaptor.kt | 4 +--- .../cloudstream3/utils/DataStoreHelper.kt | 1 + 7 files changed, 55 insertions(+), 16 deletions(-) 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 71ea374d..2f5e0050 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -118,13 +118,16 @@ interface SyncAPI : OAuth2API { ListSorting.RatingLow -> items.sortedBy { (it.personalRating ?: 0) } ListSorting.AlphabeticalA -> items.sortedBy { it.name } ListSorting.AlphabeticalZ -> items.sortedBy { it.name }.reversed() + ListSorting.UpdatedNew -> items.sortedBy { it.lastUpdatedUnixTime?.times(-1) } + ListSorting.UpdatedOld -> items.sortedBy { it.lastUpdatedUnixTime } else -> items } } } data class LibraryMetadata( - val allLibraryLists: List + val allLibraryLists: List, + val supportedListSorting: Set ) data class LibraryList( @@ -141,6 +144,7 @@ interface SyncAPI : OAuth2API { val episodesTotal: Int?, /** Out of 100 */ val personalRating: Int?, + val lastUpdatedUnixTime: Long?, override val apiName: String, override var type: TvType?, override var posterUrl: String?, 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 ed0c3f78..7d9de43a 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 @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName +import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery @@ -621,6 +622,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { this.progress, this.media.episodes, this.score, + this.updatedAt.toLong(), "AniList", TvType.Anime, this.media.coverImage.extraLarge ?: this.media.coverImage.large @@ -678,7 +680,15 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, + setOf( + ListSorting.AlphabeticalA, + ListSorting.AlphabeticalZ, + ListSorting.UpdatedNew, + ListSorting.UpdatedOld, + ListSorting.RatingHigh, + ListSorting.RatingLow, + ) ) } 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 44384c92..61e9e294 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 @@ -6,6 +6,7 @@ import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType +import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds @@ -81,7 +82,15 @@ class LocalList : SyncAPI { it.stringRes to emptyList() } return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, + setOf( + ListSorting.AlphabeticalA, + ListSorting.AlphabeticalZ, +// ListSorting.UpdatedNew, +// ListSorting.UpdatedOld, +// ListSorting.RatingHigh, +// ListSorting.RatingLow, + ) ) } 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 b91c9759..5164b606 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 @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName +import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery @@ -285,6 +286,16 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { else -> MalStatusType.None } } + + private fun parseDateLong(string: String?): Long? { + return try { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse( + string ?: return null + )?.time?.div(1000) + } catch (e: Exception) { + null + } + } } override suspend fun handleRedirect(url: String): Boolean { @@ -425,6 +436,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { this.list_status?.num_episodes_watched, this.node.num_episodes, this.list_status?.score?.times(10), + parseDateLong(this.list_status?.updated_at), "MAL", TvType.Anime, this.node.main_picture?.large ?: this.node.main_picture?.medium, @@ -488,7 +500,15 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { } return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) } + (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, + setOf( + ListSorting.AlphabeticalA, + ListSorting.AlphabeticalZ, + ListSorting.UpdatedNew, + ListSorting.UpdatedOld, + ListSorting.RatingHigh, + ListSorting.RatingLow, + ) ) } 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 d01c699a..14d31356 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 @@ -46,16 +46,10 @@ class LibraryViewModel : ViewModel() { val availableApiNames: List get() = availableSyncApis.map { it.name } - val sortingMethods = listOf( - ListSorting.RatingHigh, - ListSorting.RatingLow, -// ListSorting.UpdatedNew, -// ListSorting.UpdatedOld, - ListSorting.AlphabeticalA, - ListSorting.AlphabeticalZ, - ) + var sortingMethods = emptyList() + private set - var currentSortingMethod: ListSorting = sortingMethods.first() + var currentSortingMethod: ListSorting? = sortingMethods.firstOrNull() private set fun switchList(name: String) { @@ -90,6 +84,9 @@ class LibraryViewModel : ViewModel() { } val library = (libraryResource as? Resource.Success)?.value ?: return@let + sortingMethods = library.supportedListSorting.toList() + currentSortingMethod = null + repo.requireLibraryRefresh = false val pages = library.allLibraryLists.map { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 265489f7..649641c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -68,9 +68,7 @@ class SearchAdapter( cardList.clear() cardList.addAll(newList) - main { - diffResult.dispatchUpdatesTo(this) - } + diffResult.dispatchUpdatesTo(this) } class CardViewHolder 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 df6f209d..4e9c646f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -63,6 +63,7 @@ object DataStoreHelper { null, null, null, + null, apiName, type, posterUrl, posterHeaders, quality, id ) } From 7512dbcbf8a36ad8ea0b05e461981285667835fa Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Fri, 27 Jan 2023 22:26:24 +0100 Subject: [PATCH 22/29] Library loading fixes --- .../cloudstream3/ui/home/HomeFragment.kt | 4 +- .../ui/library/LibraryFragment.kt | 42 ++++- .../ui/library/LibraryViewModel.kt | 2 + .../ui/library/LoadingPosterAdapter.kt | 37 +++++ .../ui/quicksearch/QuickSearchFragment.kt | 2 +- .../cloudstream3/ui/result/ResultFragment.kt | 8 +- .../ui/result/ResultFragmentPhone.kt | 2 +- .../ui/result/ResultFragmentTv.kt | 2 +- .../cloudstream3/ui/search/SearchFragment.kt | 2 +- .../ui/settings/extensions/PluginsFragment.kt | 2 +- app/src/main/res/layout/fragment_library.xml | 77 +++++---- .../res/layout/loading_poster_dynamic.xml | 37 +++++ .../layout/search_result_grid_expanded.xml | 157 +++++++++--------- app/src/main/res/values/strings.xml | 1 + 14 files changed, 254 insertions(+), 121 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt create mode 100644 app/src/main/res/layout/loading_poster_dynamic.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 8a8f90b4..5cf6fc8e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -569,7 +569,7 @@ class HomeFragment : Fragment() { val mutableListOfResponse = mutableListOf() listHomepageItems.clear() - (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList( + (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList( d.values.toMutableList(), home_master_recycler ) @@ -621,7 +621,7 @@ class HomeFragment : Fragment() { //home_loaded?.isVisible = false } is Resource.Loading -> { - (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(listOf()) + (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(listOf()) home_loading_shimmer?.startShimmer() home_loading?.isVisible = true home_loading_error?.isVisible = false 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 42f0928d..97fc4b72 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 @@ -2,13 +2,18 @@ package com.lagradost.cloudstream3.ui.library import android.app.Activity import android.content.Context +import android.content.res.Configuration import android.os.Bundle +import android.os.Handler +import android.os.Looper import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ArrayAdapter import androidx.annotation.StringRes import androidx.appcompat.widget.SearchView +import androidx.core.os.postDelayed import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.google.android.material.tabs.TabLayoutMediator @@ -20,6 +25,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugAssert +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName @@ -32,7 +38,9 @@ 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 com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import kotlinx.android.synthetic.main.fragment_library.* +import kotlinx.coroutines.delay const val LIBRARY_FOLDER = "library_folder" @@ -285,9 +293,27 @@ class LibraryFragment : Fragment() { viewpager?.offscreenPageLimit = 2 viewpager?.reduceDragSensitivity() + val startLoading = Runnable { + gridview?.numColumns = context?.getSpanCount() ?: 3 + gridview?.adapter = + context?.let { LoadingPosterAdapter(it, 6 * 3) } + library_loading_overlay?.isVisible = true + library_loading_shimmer?.startShimmer() + empty_list_textview?.isVisible = false + } + + val stopLoading = Runnable { + gridview?.adapter = null + library_loading_overlay?.isVisible = false + library_loading_shimmer?.stopShimmer() + } + + val handler = Handler(Looper.getMainLooper()) + observe(libraryViewModel.pages) { resource -> when (resource) { is Resource.Success -> { + handler.removeCallbacks(startLoading) val pages = resource.value val showNotice = pages.all { it.items.isEmpty() } empty_list_textview?.isVisible = showNotice @@ -303,6 +329,11 @@ class LibraryFragment : Fragment() { // Using notifyItemRangeChanged keeps the animations when sorting viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0) + // Only stop loading after 300ms to hide the fade effect the viewpager produces when updating + // Without this there would be a flashing effect: + // loading -> show old viewpager -> black screen -> show new viewpager + handler.postDelayed(stopLoading, 300) + savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> viewpager?.setCurrentItem(currentPos, false) savedInstanceState.remove(VIEWPAGER_ITEM_KEY) @@ -314,20 +345,23 @@ class LibraryFragment : Fragment() { ) { tab, position -> tab.text = pages.getOrNull(position)?.title?.asStringNull(context) }.attach() - loading_indicator?.hide() } is Resource.Loading -> { - loading_indicator?.show() - empty_list_textview?.isVisible = false + // Only start loading after 200ms to prevent loading cached lists + handler.postDelayed(startLoading, 200) } is Resource.Failure -> { + stopLoading.run() // No user indication it failed :( // TODO - loading_indicator?.hide() } } } } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + } } class MenuSearchView(context: Context) : SearchView(context) { 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 14d31356..07f88fbf 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 @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import kotlinx.coroutines.delay enum class ListSorting(@StringRes val stringRes: Int) { Query(R.string.none), @@ -96,6 +97,7 @@ class LibraryViewModel : ViewModel() { ) } + delay(5000) _pages.postValue(Resource.Success(pages)) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt new file mode 100644 index 00000000..a637133b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.ui.library + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.ListPopupWindow.MATCH_PARENT +import android.widget.RelativeLayout +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.UIHelper.toPx +import kotlinx.android.synthetic.main.loading_poster_dynamic.view.* +import kotlin.math.roundToInt +import kotlin.math.sqrt + +class LoadingPosterAdapter(context: Context, private val itemCount: Int) : + BaseAdapter() { + private val inflater: LayoutInflater = LayoutInflater.from(context) + + override fun getCount(): Int { + return itemCount + } + + override fun getItem(position: Int): Any? { + return null + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + return convertView ?: inflater.inflate(R.layout.loading_poster_dynamic, parent, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index ad3d9eb8..ba57d2de 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -220,7 +220,7 @@ class QuickSearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - (quick_search_autofit_results?.adapter as? SearchAdapter?)?.updateList( + (quick_search_autofit_results?.adapter as? SearchAdapter)?.updateList( context?.filterSearchResultByFilmQuality(data) ?: data ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 9cfbf45c..2e2e46b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -277,7 +277,7 @@ open class ResultFragment : ResultTrailerPlayer() { private var downloadButton: EasyDownloadButton? = null override fun onDestroyView() { updateUIListener = null - (result_episodes?.adapter as EpisodeAdapter?)?.killAdapter() + (result_episodes?.adapter as? EpisodeAdapter)?.killAdapter() downloadButton?.dispose() super.onDestroyView() @@ -458,7 +458,7 @@ open class ResultFragment : ResultTrailerPlayer() { temporary_no_focus?.requestFocus() } - (result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value) + (result_episodes?.adapter as? EpisodeAdapter)?.updateList(episodes.value) if (isTv && hasEpisodes) main { delay(500) @@ -687,7 +687,7 @@ open class ResultFragment : ResultTrailerPlayer() { val newList = list.filter { it.isSynced && it.hasAccount } result_mini_sync?.isVisible = newList.isNotEmpty() - (result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.mapNotNull { it.icon }) + (result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon }) } var currentSyncProgress = 0 @@ -900,7 +900,7 @@ open class ResultFragment : ResultTrailerPlayer() { result_cast_items?.isVisible = d.actors != null - (result_cast_items?.adapter as ActorAdaptor?)?.apply { + (result_cast_items?.adapter as? ActorAdaptor)?.apply { updateList(d.actors ?: emptyList()) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 9bae8753..b38e1765 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -485,7 +485,7 @@ class ResultFragmentPhone : ResultFragment() { result_recommendations?.post { rec?.let { list -> - (result_recommendations?.adapter as SearchAdapter?)?.updateList(list.filter { it.apiName == matchAgainst }) + (result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst }) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index d5cab1a6..2bd8ff0f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -107,7 +107,7 @@ class ResultFragmentTv : ResultFragment() { result_recommendations?.isGone = isInvalid result_recommendations_holder?.isGone = isInvalid val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName - (result_recommendations?.adapter as SearchAdapter?)?.updateList(rec?.filter { it.apiName == matchAgainst } + (result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst } ?: emptyList()) rec?.map { it.apiName }?.distinct()?.let { apiNames -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 4144a042..b4a38216 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -420,7 +420,7 @@ class SearchFragment : Fragment() { is Resource.Success -> { it.value.let { data -> if (data.isNotEmpty()) { - (search_autofit_results?.adapter as SearchAdapter?)?.updateList(data) + (search_autofit_results?.adapter as? SearchAdapter)?.updateList(data) } } searchExitIcon.alpha = 1f diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index bd44a058..d328d226 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -143,7 +143,7 @@ class PluginsFragment : Fragment() { } observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) -> - (plugin_recycler_view?.adapter as? PluginAdapter?)?.updateList(list) + (plugin_recycler_view?.adapter as? PluginAdapter)?.updateList(list) if (scrollToTop) plugin_recycler_view?.scrollToPosition(0) diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 116a4e03..28f21bf6 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -89,23 +89,53 @@ - - - - - - - - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + + + + + + + + + + - - - - - - - diff --git a/app/src/main/res/layout/loading_poster_dynamic.xml b/app/src/main/res/layout/loading_poster_dynamic.xml new file mode 100644 index 00000000..11855acb --- /dev/null +++ b/app/src/main/res/layout/loading_poster_dynamic.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/search_result_grid_expanded.xml b/app/src/main/res/layout/search_result_grid_expanded.xml index d6deec2f..47fd7cd3 100644 --- a/app/src/main/res/layout/search_result_grid_expanded.xml +++ b/app/src/main/res/layout/search_result_grid_expanded.xml @@ -10,95 +10,104 @@ android:foreground="@drawable/outline_drawable" android:orientation="vertical"> - + android:layout_height="wrap_content"> - + android:layout_height="0dp" + android:layout_margin="2dp" + android:layout_marginBottom="2dp" + android:elevation="10dp" + app:cardBackgroundColor="?attr/primaryGrayBackground" + app:cardCornerRadius="@dimen/rounded_image_radius" + app:layout_constraintDimensionRatio="1:1.414" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - - + + android:id="@+id/text_quality" + style="@style/TypeButton" /> - - - + android:orientation="vertical"> + + + + + + + + + + - + android:layout_gravity="end" + android:background="@color/transparent" + android:textSize="20sp" + android:visibility="gone" + tools:text="πŸ‡ΈπŸ‡ͺ" + tools:visibility="visible" /> + - - - - - - + + PackageInstaller App will be updated upon exit Sort by + Sort Rating (High to Low) Rating (Low to High) Updated (New to Old) From ef7e0ecf0dd8aa1fc0827d0bd419be1f9e6093ab Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 28 Jan 2023 00:54:47 +0100 Subject: [PATCH 23/29] Fixed rotation and loading delay --- .../ui/library/LibraryFragment.kt | 1 + .../ui/library/LibraryViewModel.kt | 1 - .../ui/library/ViewpagerAdapter.kt | 20 ++++++++++++++----- 3 files changed, 16 insertions(+), 6 deletions(-) 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 97fc4b72..5e2e5c45 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 @@ -360,6 +360,7 @@ class LibraryFragment : Fragment() { } override fun onConfigurationChanged(newConfig: Configuration) { + (viewpager.adapter as? ViewpagerAdapter)?.rebind() super.onConfigurationChanged(newConfig) } } 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 07f88fbf..5f64880c 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 @@ -97,7 +97,6 @@ class LibraryViewModel : ViewModel() { ) } - delay(5000) _pages.postValue(Resource.Success(pages)) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 3e31e875..33a40386 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -28,18 +28,28 @@ class ViewpagerAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is PageViewHolder -> { - holder.bind(pages[position]) + holder.bind(pages[position], unbound.remove(position)) } } } + private val unbound = mutableSetOf() + /** + * Used to mark all pages for re-binding and forces all items to be refreshed + * Without this the pages will still use the same adapters + **/ + fun rebind() { + unbound.addAll(0..pages.size) + this.notifyItemRangeChanged(0, pages.size) + } + inner class PageViewHolder(private val itemViewTest: View) : RecyclerView.ViewHolder(itemViewTest) { - fun bind(page: SyncAPI.Page) { - if (itemViewTest.page_recyclerview?.adapter == null) { - itemView.page_recyclerview?.spanCount = - this@PageViewHolder.itemView.context.getSpanCount() ?: 3 + fun bind(page: SyncAPI.Page, rebind: Boolean) { + itemView.page_recyclerview?.spanCount = + this@PageViewHolder.itemView.context.getSpanCount() ?: 3 + if (itemViewTest.page_recyclerview?.adapter == null || rebind) { // Only add the items after it has been attached since the items rely on ItemWidth // Which is only determined after the recyclerview is attached. // If this fails then item height becomes 0 when there is only one item From e875d520deb56eeab03858dbd15e31e1cd39bc88 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 28 Jan 2023 01:11:13 +0100 Subject: [PATCH 24/29] Fixed loading padding --- app/src/main/res/layout/fragment_library.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 28f21bf6..c9b1e3a0 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -103,21 +103,19 @@ tools:listitem="@layout/library_viewpager_page" /> + android:background="?attr/primaryBlackBackground" + android:visibility="gone" + tools:visibility="visible"> From e7732cc15fead262cb0f904a535a9e3d7c84cc37 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 28 Jan 2023 14:57:06 +0100 Subject: [PATCH 25/29] Smoother fast scrolling in library & no labels --- .../ui/library/LibraryFragment.kt | 29 ++++++++++++++++--- app/src/main/res/layout/activity_main.xml | 4 +-- app/src/main/res/layout/fragment_library.xml | 1 + app/src/main/res/menu/bottom_nav_menu.xml | 6 ++-- 4 files changed, 31 insertions(+), 9 deletions(-) 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 5e2e5c45..7f025b74 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 @@ -10,10 +10,9 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter +import android.view.animation.AlphaAnimation import androidx.annotation.StringRes import androidx.appcompat.widget.SearchView -import androidx.core.os.postDelayed import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.google.android.material.tabs.TabLayoutMediator @@ -25,7 +24,6 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugAssert -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName @@ -40,7 +38,7 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import kotlinx.android.synthetic.main.fragment_library.* -import kotlinx.coroutines.delay +import kotlin.math.abs const val LIBRARY_FOLDER = "library_folder" @@ -339,11 +337,34 @@ class LibraryFragment : Fragment() { savedInstanceState.remove(VIEWPAGER_ITEM_KEY) } + // Since the animation to scroll multiple items is so much its better to just hide + // the viewpager a bit while the fastest animation is running + fun hideViewpager(distance: Int) { + if (distance < 3) return + + val hideAnimation = AlphaAnimation(1f, 0f).apply { + duration = distance * 50L + fillAfter = true + } + val showAnimation = AlphaAnimation(0f, 1f).apply { + duration = distance * 50L + startOffset = distance * 100L + fillAfter = true + } + viewpager?.startAnimation(hideAnimation) + viewpager?.startAnimation(showAnimation) + } + TabLayoutMediator( library_tab_layout, viewpager, ) { tab, position -> tab.text = pages.getOrNull(position)?.title?.asStringNull(context) + tab.view.setOnClickListener { + val currentItem = viewpager?.currentItem ?: return@setOnClickListener + val distance = abs(position - currentItem) + hideViewpager(distance) + } }.attach() } is Resource.Loading -> { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ad29d22a..b6290865 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -35,9 +35,9 @@ --> + android:id="@+id/navigation_home" + android:icon="@drawable/home_alt" + android:title="@string/title_home" /> Date: Sat, 28 Jan 2023 22:49:58 +0100 Subject: [PATCH 26/29] fix library statusbar --- .../com/lagradost/cloudstream3/ui/library/LibraryFragment.kt | 2 +- app/src/main/res/layout/fragment_library.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 7f025b74..4b89e984 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 @@ -88,7 +88,7 @@ class LibraryFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - context?.fixPaddingStatusbar(library_root) + context?.fixPaddingStatusbar(search_status_bar_padding) sort_fab?.setOnClickListener { val methods = libraryViewModel.sortingMethods.map { diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 7230a34b..f9012148 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -24,6 +24,7 @@ android:background="?attr/primaryGrayBackground"> Date: Sat, 28 Jan 2023 23:08:55 +0100 Subject: [PATCH 27/29] mini fix --- .../com/lagradost/cloudstream3/ui/library/LibraryFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4b89e984..1c6af447 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 @@ -186,7 +186,7 @@ class LibraryFragment : Fragment() { items, selectedIndex, txt(R.string.open_with).asString(this), - true, + false, {}, ) { val savedData = if (it < baseOptions.size) { From df95931d0ade28f8ef1784864266d8e4d0e5c02f Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Sat, 28 Jan 2023 23:36:24 +0100 Subject: [PATCH 28/29] future proofed SyncID --- .../com/lagradost/cloudstream3/syncproviders/SyncAPI.kt | 6 +++++- .../cloudstream3/syncproviders/providers/LocalList.kt | 2 +- .../com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) 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 2f5e0050..8c76c5bf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -138,7 +138,11 @@ interface SyncAPI : OAuth2API { data class LibraryItem( override val name: String, override val url: String, - /** Unique unchanging string used for data storage */ + /** + * Unique unchanging string used for data storage. + * This should be the actual id when you change scores and status + * since score changes from library might get added in the future. + **/ val syncId: String, val episodesCompleted: Int?, val episodesTotal: Int?, 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 61e9e294..0b081220 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 @@ -72,7 +72,7 @@ class LocalList : SyncAPI { it.second.stringRes }.mapValues { group -> group.value.mapNotNull { - getBookmarkedData(it.first)?.toLibraryItem() + getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) } } } 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 4e9c646f..281c9c44 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -55,16 +55,16 @@ object DataStoreHelper { @JsonProperty("quality") override var quality: SearchQuality? = null, @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, ) : SearchResponse { - fun toLibraryItem(): SyncAPI.LibraryItem { + fun toLibraryItem(id: String): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( name, url, - url, + id, null, null, null, null, - apiName, type, posterUrl, posterHeaders, quality, id + apiName, type, posterUrl, posterHeaders, quality, this.id ) } } From 7a1cfc2f2b7bcc749fb35a3d5bf70245b032cf95 Mon Sep 17 00:00:00 2001 From: reduplicated <110570621+reduplicated@users.noreply.github.com> Date: Thu, 2 Feb 2023 18:30:48 +0100 Subject: [PATCH 29/29] mini fix to text in popup --- .../java/com/lagradost/cloudstream3/MainActivity.kt | 7 ++----- .../cloudstream3/utils/SingleSelectionHelper.kt | 11 ----------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 5720b7a7..8b5ef879 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -63,10 +63,7 @@ import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.home.HomeViewModel -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setText +import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings @@ -797,7 +794,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { resultview_preview_meta_duration.setText(d.durationText) resultview_preview_meta_rating.setText(d.ratingText) - resultview_preview_description?.setText(d.plotText) + resultview_preview_description?.setTextHtml(d.plotText) resultview_preview_poster?.setImage( d.posterImage ?: d.posterBackgroundImage ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 2dc6846c..1f6d726d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -250,17 +250,6 @@ object SingleSelectionHelper { ) } - fun showBottomDialog( - items: List, - selectedIndex: Int, - name: String, - showApply: Boolean, - dismissCallback: () -> Unit, - callback: (Int) -> Unit, - ) { - - } - /** Only for a low amount of items */ fun Activity?.showBottomDialog( items: List,