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 1/9] 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 2/9] 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 3/9] 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 @@