mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
library work in progress
This commit is contained in:
parent
ec9a39814b
commit
67fe6730e0
26 changed files with 692 additions and 90 deletions
|
@ -177,6 +177,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
val isNavVisible = listOf(
|
val isNavVisible = listOf(
|
||||||
R.id.navigation_home,
|
R.id.navigation_home,
|
||||||
R.id.navigation_search,
|
R.id.navigation_search,
|
||||||
|
R.id.navigation_library,
|
||||||
R.id.navigation_downloads,
|
R.id.navigation_downloads,
|
||||||
R.id.navigation_settings,
|
R.id.navigation_settings,
|
||||||
R.id.navigation_download_child,
|
R.id.navigation_download_child,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
||||||
|
|
||||||
interface SyncAPI : OAuth2API {
|
interface SyncAPI : OAuth2API {
|
||||||
val mainUrl: String
|
val mainUrl: String
|
||||||
|
@ -22,6 +23,8 @@ interface SyncAPI : OAuth2API {
|
||||||
|
|
||||||
suspend fun search(name: String): List<SyncSearchResult>?
|
suspend fun search(name: String): List<SyncSearchResult>?
|
||||||
|
|
||||||
|
suspend fun getPersonalLibrary(): List<LibraryItem>?
|
||||||
|
|
||||||
fun getIdFromUrl(url : String) : String
|
fun getIdFromUrl(url : String) : String
|
||||||
|
|
||||||
data class SyncSearchResult(
|
data class SyncSearchResult(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.lagradost.cloudstream3.ErrorLoadingException
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.ui.library.LibraryItem
|
||||||
|
|
||||||
class SyncRepo(private val repo: SyncAPI) {
|
class SyncRepo(private val repo: SyncAPI) {
|
||||||
val idPrefix = repo.idPrefix
|
val idPrefix = repo.idPrefix
|
||||||
|
@ -16,21 +17,25 @@ class SyncRepo(private val repo: SyncAPI) {
|
||||||
return safeApiCall { repo.score(id, status) }
|
return safeApiCall { repo.score(id, status) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getStatus(id : String) : Resource<SyncAPI.SyncStatus> {
|
suspend fun getStatus(id: String): Resource<SyncAPI.SyncStatus> {
|
||||||
return safeApiCall { repo.getStatus(id) ?: throw ErrorLoadingException("No data") }
|
return safeApiCall { repo.getStatus(id) ?: throw ErrorLoadingException("No data") }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getResult(id : String) : Resource<SyncAPI.SyncResult> {
|
suspend fun getResult(id: String): Resource<SyncAPI.SyncResult> {
|
||||||
return safeApiCall { repo.getResult(id) ?: throw ErrorLoadingException("No data") }
|
return safeApiCall { repo.getResult(id) ?: throw ErrorLoadingException("No data") }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun search(query : String) : Resource<List<SyncAPI.SyncSearchResult>> {
|
suspend fun search(query: String): Resource<List<SyncAPI.SyncSearchResult>> {
|
||||||
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
|
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasAccount() : Boolean {
|
suspend fun getPersonalLibrary(): Resource<List<LibraryItem>> {
|
||||||
|
return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasAccount(): Boolean {
|
||||||
return normalSafeApiCall { repo.loginInfo() != null } ?: false
|
return normalSafeApiCall { repo.loginInfo() != null } ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIdFromUrl(url : String) : String = repo.getIdFromUrl(url)
|
fun getIdFromUrl(url: String): String = repo.getIdFromUrl(url)
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
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.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
@ -595,7 +596,26 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
@JsonProperty("score") val score: Int,
|
@JsonProperty("score") val score: Int,
|
||||||
@JsonProperty("private") val private: Boolean,
|
@JsonProperty("private") val private: Boolean,
|
||||||
@JsonProperty("media") val media: Media
|
@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(
|
data class Lists(
|
||||||
@JsonProperty("status") val status: String?,
|
@JsonProperty("status") val status: String?,
|
||||||
|
@ -630,6 +650,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
||||||
|
return getAnilistAnimeListSmart()?.map { it.entries.mapNotNull { entry -> entry.toLibraryItem(entry.status ?: it.status) } }?.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getFullAnilistList(): FullAnilistList? {
|
private suspend fun getFullAnilistList(): FullAnilistList? {
|
||||||
var userID: Int? = null
|
var userID: Int? = null
|
||||||
/** WARNING ASSUMES ONE USER! **/
|
/** WARNING ASSUMES ONE USER! **/
|
||||||
|
|
|
@ -7,11 +7,13 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.ShowStatus
|
import com.lagradost.cloudstream3.ShowStatus
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
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.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||||
|
@ -381,7 +383,22 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
data class Data(
|
data class Data(
|
||||||
@JsonProperty("node") val node: Node,
|
@JsonProperty("node") val node: Node,
|
||||||
@JsonProperty("list_status") val list_status: ListStatus?,
|
@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(
|
data class Paging(
|
||||||
@JsonProperty("next") val next: String?
|
@JsonProperty("next") val next: String?
|
||||||
|
@ -424,6 +441,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPersonalLibrary(): List<LibraryItem>? {
|
||||||
|
return getMalAnimeListSmart()?.map { it.toLibraryItem() }
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getMalAnimeList(): Array<Data> {
|
private suspend fun getMalAnimeList(): Array<Data> {
|
||||||
checkMalToken()
|
checkMalToken()
|
||||||
var offset = 0
|
var offset = 0
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<List<Page>> = MutableLiveData(emptyList())
|
||||||
|
val pages: LiveData<List<Page>> = _pages
|
||||||
|
|
||||||
|
private val _currentApiName: MutableLiveData<String> = MutableLiveData("")
|
||||||
|
val currentApiName: LiveData<String> = _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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<LibraryItem>,
|
||||||
|
) :
|
||||||
|
AppUtils.DiffAdapter<LibraryItem>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<LibraryItem>
|
||||||
|
) {
|
||||||
|
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<String, String>?,
|
||||||
|
override var id: Int?,
|
||||||
|
override var quality: SearchQuality?,
|
||||||
|
) : SearchResponse
|
||||||
|
|
||||||
|
|
||||||
|
class ViewpagerAdapter(var pages: List<Page>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import androidx.core.text.HtmlCompat
|
||||||
import androidx.core.text.toSpanned
|
import androidx.core.text.toSpanned
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.tvprovider.media.tv.PreviewChannelHelper
|
import androidx.tvprovider.media.tv.PreviewChannelHelper
|
||||||
|
@ -64,6 +65,7 @@ import okhttp3.Cache
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
object AppUtils {
|
object AppUtils {
|
||||||
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
||||||
|
@ -263,6 +265,50 @@ object AppUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class DiffAdapter<T>(
|
||||||
|
open val items: MutableList<T>,
|
||||||
|
val comparison: (first: T, second: T) -> Boolean = { first, second ->
|
||||||
|
first.hashCode() == second.hashCode()
|
||||||
|
}
|
||||||
|
) :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateList(newList: List<T>) {
|
||||||
|
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<T>,
|
||||||
|
private val newList: List<T>
|
||||||
|
) :
|
||||||
|
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) {
|
fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val context = this
|
val context = this
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
|
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
|
||||||
|
<item android:color="?attr/colorPrimary" android:state_focused="true"/>
|
||||||
|
<item android:color="?attr/colorPrimary" android:state_selected="true"/>
|
||||||
<item android:color="?attr/grayTextColor" android:state_checked="false"/>
|
<item android:color="?attr/grayTextColor" android:state_checked="false"/>
|
||||||
</selector>
|
</selector>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,12l-2.5,-1.5L15,12L15,4h5v8z"/>
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_baseline_sort_24.xml
Normal file
5
app/src/main/res/drawable/ic_baseline_sort_24.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:autoMirrored="true" android:height="24dp"
|
||||||
|
android:tint="?attr/white" android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||||
|
</vector>
|
|
@ -1,5 +1,5 @@
|
||||||
<vector android:height="24dp" android:tint="?attr/white"
|
<vector android:height="12dp" android:tint="?attr/white"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
6
app/src/main/res/drawable/indicator_background.xml
Normal file
6
app/src/main/res/drawable/indicator_background.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/textColor"/>
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
6
app/src/main/res/drawable/rating_bg_color.xml
Normal file
6
app/src/main/res/drawable/rating_bg_color.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/ratingColorBg"/>
|
||||||
|
<corners android:radius="@dimen/rounded_image_radius"/>
|
||||||
|
<!-- <stroke android:color="@color/subColor" android:width="2dp"/>-->
|
||||||
|
</shape>
|
63
app/src/main/res/layout/fragment_library.xml
Normal file
63
app/src/main/res/layout/fragment_library.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/library_root"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/library_tab_layout"
|
||||||
|
style="@style/Theme.Widget.Tabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
|
||||||
|
app:tabIndicatorHeight="30dp"
|
||||||
|
app:tabIndicatorGravity="center"
|
||||||
|
app:tabIndicatorColor="@color/textColor"
|
||||||
|
app:tabIndicator="@drawable/indicator_background"
|
||||||
|
android:descendantFocusability="blocksDescendants"
|
||||||
|
android:focusable="false"
|
||||||
|
app:tabBackground="?attr/primaryGrayBackground"
|
||||||
|
app:tabGravity="center"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabTextColor="@color/textColor"
|
||||||
|
app:tabSelectedTextColor="@color/lightTextColor"
|
||||||
|
app:tabTextAppearance="@style/TabNoCaps" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2 xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/viewpager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_above="@+id/menu_toolbar"
|
||||||
|
android:layout_below="@+id/library_tab_layout"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:listitem="@layout/library_viewpager_page">
|
||||||
|
|
||||||
|
</androidx.viewpager2.widget.ViewPager2>
|
||||||
|
|
||||||
|
<!-- <com.google.android.material.appbar.AppBarLayout-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:background="?attr/primaryGrayBackground"-->
|
||||||
|
<!-- android:backgroundTint="?attr/primaryGrayBackground"-->
|
||||||
|
<!-- app:elevation="0dp">-->
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/menu_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
android:backgroundTint="?attr/primaryGrayBackground"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:menu="@menu/library_menu">
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
<!-- </com.google.android.material.appbar.AppBarLayout>-->
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
17
app/src/main/res/layout/library_viewpager_page.xml
Normal file
17
app/src/main/res/layout/library_viewpager_page.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
|
android:id="@+id/page_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:listitem="@layout/home_result_grid_expanded" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
|
@ -1,90 +1,111 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/search_result_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
|
||||||
|
|
||||||
android:foreground="@drawable/outline_drawable"
|
|
||||||
android:focusable="true"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:id="@+id/search_result_root">
|
android:focusable="true"
|
||||||
|
android:foreground="@drawable/outline_drawable"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_margin="2dp"
|
android:id="@+id/background_card"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="2dp"
|
||||||
android:layout_marginBottom="2dp"
|
android:layout_marginBottom="2dp"
|
||||||
android:elevation="10dp"
|
android:elevation="10dp"
|
||||||
|
app:cardBackgroundColor="?attr/primaryGrayBackground"
|
||||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||||
android:id="@+id/background_card"
|
app:layout_constraintBottom_toTopOf="@+id/imageText"
|
||||||
app:cardBackgroundColor="?attr/primaryGrayBackground">
|
app:layout_constraintDimensionRatio="0.6333:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageView"
|
||||||
tools:src="@drawable/example_poster"
|
|
||||||
|
|
||||||
android:duplicateParentState="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@string/search_poster_img_des"
|
||||||
|
android:duplicateParentState="true"
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="@string/search_poster_img_des" />
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/example_poster" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
tools:text="@string/quality_hd"
|
|
||||||
android:id="@+id/text_quality"
|
android:id="@+id/text_quality"
|
||||||
android:textColor="@color/textColor"
|
|
||||||
style="@style/SearchBox"
|
style="@style/SearchBox"
|
||||||
android:background="@drawable/type_bg_color" />
|
android:background="@drawable/type_bg_color"
|
||||||
|
android:textColor="@color/textColor"
|
||||||
|
tools:text="@string/quality_hd" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@string/app_dubbed_text"
|
|
||||||
android:id="@+id/text_is_dub"
|
android:id="@+id/text_is_dub"
|
||||||
android:layout_gravity="end"
|
|
||||||
style="@style/SearchBox"
|
style="@style/SearchBox"
|
||||||
android:background="@drawable/dub_bg_color" />
|
android:layout_gravity="end"
|
||||||
|
android:background="@drawable/dub_bg_color"
|
||||||
|
android:text="@string/app_dubbed_text" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_is_sub"
|
android:id="@+id/text_is_sub"
|
||||||
android:text="@string/app_subbed_text"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
style="@style/SearchBox"
|
style="@style/SearchBox"
|
||||||
android:background="@drawable/sub_bg_color" />
|
android:layout_gravity="end"
|
||||||
|
android:background="@drawable/sub_bg_color"
|
||||||
|
android:text="@string/app_subbed_text" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
tools:visibility="visible"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:id="@+id/text_flag"
|
android:id="@+id/text_flag"
|
||||||
tools:text="🇸🇪"
|
|
||||||
style="@style/SearchBox"
|
style="@style/SearchBox"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:background="@color/transparent" />
|
android:background="@color/transparent"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="🇸🇪"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
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" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
tools:text="The Perfect Run\nThe Perfect Run"
|
android:id="@+id/imageText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="13sp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:paddingBottom="5dp"
|
android:layout_weight="0"
|
||||||
android:paddingTop="5dp"
|
android:ellipsize="end"
|
||||||
android:textColor="?attr/textColor"
|
android:gravity="center"
|
||||||
android:id="@+id/imageText"
|
|
||||||
android:minLines="2"
|
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
|
android:minLines="2"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
android:paddingEnd="5dp"
|
android:paddingEnd="5dp"
|
||||||
android:ellipsize="end" />
|
android:paddingBottom="5dp"
|
||||||
</LinearLayout>
|
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" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,17 +4,21 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_home"
|
android:id="@+id/navigation_home"
|
||||||
android:icon="@drawable/ic_outline_home_24"
|
android:icon="@drawable/ic_outline_home_24"
|
||||||
android:title="@string/title_home"/>
|
android:title="@string/title_home" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_search"
|
android:id="@+id/navigation_search"
|
||||||
android:icon="@drawable/search_icon"
|
android:icon="@drawable/search_icon"
|
||||||
android:title="@string/title_search"/>
|
android:title="@string/title_search" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/navigation_library"
|
||||||
|
android:icon="@drawable/ic_baseline_collections_bookmark_24"
|
||||||
|
android:title="@string/library" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_downloads"
|
android:id="@+id/navigation_downloads"
|
||||||
android:icon="@drawable/netflix_download"
|
android:icon="@drawable/netflix_download"
|
||||||
android:title="@string/title_downloads"/>
|
android:title="@string/title_downloads" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_settings"
|
android:id="@+id/navigation_settings"
|
||||||
android:icon="@drawable/ic_outline_settings_24"
|
android:icon="@drawable/ic_outline_settings_24"
|
||||||
android:title="@string/title_settings"/>
|
android:title="@string/title_settings" />
|
||||||
</menu>
|
</menu>
|
17
app/src/main/res/menu/library_menu.xml
Normal file
17
app/src/main/res/menu/library_menu.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/search_button"
|
||||||
|
android:icon="@drawable/search_icon"
|
||||||
|
android:title="@string/title_search"
|
||||||
|
app:searchHintIcon="@drawable/search_icon"
|
||||||
|
app:showAsAction="collapseActionView|ifRoom"
|
||||||
|
app:actionViewClass="com.lagradost.cloudstream3.ui.library.MenuSearchView" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/sort_button"
|
||||||
|
android:icon="@drawable/ic_baseline_sort_24"
|
||||||
|
android:title="Sort"
|
||||||
|
app:showAsAction="collapseActionView|ifRoom" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -144,6 +144,15 @@
|
||||||
app:popEnterAnim="@anim/enter_anim"
|
app:popEnterAnim="@anim/enter_anim"
|
||||||
app:popExitAnim="@anim/exit_anim" />
|
app:popExitAnim="@anim/exit_anim" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/navigation_library"
|
||||||
|
android:name="com.lagradost.cloudstream3.ui.library.LibraryFragment"
|
||||||
|
android:label="@string/library"
|
||||||
|
app:enterAnim="@anim/enter_anim"
|
||||||
|
app:exitAnim="@anim/exit_anim"
|
||||||
|
app:popEnterAnim="@anim/enter_anim"
|
||||||
|
app:popExitAnim="@anim/exit_anim" />
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/navigation_settings_general"
|
android:id="@+id/navigation_settings_general"
|
||||||
android:name="com.lagradost.cloudstream3.ui.settings.SettingsGeneral"
|
android:name="com.lagradost.cloudstream3.ui.settings.SettingsGeneral"
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<color name="subColorBg">#F53B66</color>
|
<color name="subColorBg">#F53B66</color>
|
||||||
<color name="typeColor">#3BF585</color>
|
<color name="typeColor">#3BF585</color>
|
||||||
<color name="typeColorBg">?attr/colorPrimaryDark</color>
|
<color name="typeColorBg">?attr/colorPrimaryDark</color>
|
||||||
|
<color name="ratingColorBg">#3F51B5</color>
|
||||||
|
|
||||||
<color name="adultColor">#FF6F63</color> <!-- same as sub color -->
|
<color name="adultColor">#FF6F63</color> <!-- same as sub color -->
|
||||||
|
|
||||||
|
|
|
@ -253,6 +253,7 @@
|
||||||
<string name="backup_failed_error_format">Error backing up %s</string>
|
<string name="backup_failed_error_format">Error backing up %s</string>
|
||||||
|
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
|
<string name="library">Library</string>
|
||||||
<string name="category_account">Accounts</string>
|
<string name="category_account">Accounts</string>
|
||||||
<string name="category_updates">Updates and backup</string>
|
<string name="category_updates">Updates and backup</string>
|
||||||
|
|
||||||
|
@ -615,4 +616,10 @@
|
||||||
<string name="safe_mode_title">Safe Mode enabled</string>
|
<string name="safe_mode_title">Safe Mode enabled</string>
|
||||||
<string name="safe_mode_description">An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble.</string>
|
<string name="safe_mode_description">An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble.</string>
|
||||||
<string name="safe_mode_crash_info">View crash info</string>
|
<string name="safe_mode_crash_info">View crash info</string>
|
||||||
|
<string name="sort_rating_desc">Rating (High to Low)</string>
|
||||||
|
<string name="sort_rating_asc">Rating (Low to High)</string>
|
||||||
|
<string name="sort_updated_new">Updated (New to Old)</string>
|
||||||
|
<string name="sort_updated_old">Updated (Old to New)</string>
|
||||||
|
<string name="sort_alphabetical_a">Alphabetical (A to Z)</string>
|
||||||
|
<string name="sort_alphabetical_z">Alphabetical (Z to A)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -299,6 +299,10 @@
|
||||||
<item name="textAllCaps">false</item>
|
<item name="textAllCaps">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TabNoCaps" parent="TextAppearance.Design.Tab">
|
||||||
|
<item name="textAllCaps">false</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="AppTextViewStyle" parent="android:Widget.TextView">
|
<style name="AppTextViewStyle" parent="android:Widget.TextView">
|
||||||
<item name="android:fontFamily">@font/google_sans</item>
|
<item name="android:fontFamily">@font/google_sans</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue