forked from recloudstream/cloudstream
Reworked backend to make list titles use UiText
This commit is contained in:
parent
9bc90438ec
commit
26320bb535
9 changed files with 106 additions and 89 deletions
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.syncproviders
|
|||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||
import com.lagradost.cloudstream3.ui.result.UiText
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
|
||||
enum class SyncIdName {
|
||||
|
@ -101,7 +102,7 @@ interface SyncAPI : OAuth2API {
|
|||
|
||||
|
||||
data class Page(
|
||||
val title: String, var items: List<LibraryItem>
|
||||
val title: UiText, var items: List<LibraryItem>
|
||||
) {
|
||||
fun sort(method: ListSorting?, query: String? = null) {
|
||||
items = when (method) {
|
||||
|
@ -123,11 +124,12 @@ interface SyncAPI : OAuth2API {
|
|||
}
|
||||
|
||||
data class LibraryMetadata(
|
||||
/** List of all available pages, useful to show empty pages
|
||||
* if the user has no entry on that page */
|
||||
val allListNames: List<String>,
|
||||
/** Not necessarily sorted list of all library items, will be grouped by listName */
|
||||
val allLibraryItems: List<LibraryItem>
|
||||
val allLibraryLists: List<LibraryList>
|
||||
)
|
||||
|
||||
data class LibraryList(
|
||||
val name: UiText,
|
||||
val items: List<LibraryItem>
|
||||
)
|
||||
|
||||
data class LibraryItem(
|
||||
|
@ -135,7 +137,6 @@ interface SyncAPI : OAuth2API {
|
|||
override val url: String,
|
||||
/** Unique unchanging string used for data storage */
|
||||
val syncId: String,
|
||||
val listName: String,
|
||||
val episodesCompleted: Int?,
|
||||
val episodesTotal: Int?,
|
||||
/** Out of 100 */
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.capitalize
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
|
@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager
|
|||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
|
@ -316,14 +317,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
// Changing names of these will show up in UI
|
||||
enum class AniListStatusType(var value: Int) {
|
||||
Watching(0),
|
||||
Completed(1),
|
||||
Paused(2),
|
||||
Dropped(3),
|
||||
Planning(4),
|
||||
ReWatching(5),
|
||||
None(-1)
|
||||
enum class AniListStatusType(var value: Int, @StringRes val stringRes: Int) {
|
||||
Watching(0, R.string.type_watching),
|
||||
Completed(1, R.string.type_completed),
|
||||
Paused(2, R.string.type_on_hold),
|
||||
Dropped(3, R.string.type_dropped),
|
||||
Planning(4, R.string.type_plan_to_watch),
|
||||
ReWatching(5, R.string.type_re_watching),
|
||||
None(-1, R.string.none)
|
||||
}
|
||||
|
||||
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
|
@ -339,7 +340,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun convertAnilistStringToStatus(string: String): AniListStatusType {
|
||||
fun convertAniListStringToStatus(string: String): AniListStatusType {
|
||||
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
|
||||
}
|
||||
|
||||
|
@ -609,7 +610,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("private") val private: Boolean,
|
||||
@JsonProperty("media") val media: Media
|
||||
) {
|
||||
fun toLibraryItem(listName: String?): SyncAPI.LibraryItem? {
|
||||
fun toLibraryItem(): SyncAPI.LibraryItem {
|
||||
return SyncAPI.LibraryItem(
|
||||
// English title first
|
||||
this.media.title.english ?: this.media.title.romaji
|
||||
|
@ -617,7 +618,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
?: "",
|
||||
"https://anilist.co/anime/${this.media.id}/",
|
||||
this.media.id.toString(),
|
||||
listName?.lowercase()?.capitalize() ?: return null,
|
||||
this.progress,
|
||||
this.media.episodes,
|
||||
this.score,
|
||||
|
@ -665,15 +665,20 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
|
||||
return SyncAPI.LibraryMetadata(
|
||||
emptyList(),
|
||||
getAniListAnimeListSmart()?.map {
|
||||
it.entries.mapNotNull { entry ->
|
||||
entry.toLibraryItem(
|
||||
entry.status ?: it.status
|
||||
)
|
||||
val list = getAniListAnimeListSmart()?.groupBy {
|
||||
convertAniListStringToStatus(it.status ?: "").stringRes
|
||||
}?.mapValues { group ->
|
||||
group.value.map { it.entries.map { entry -> entry.toLibraryItem() } }.flatten()
|
||||
} ?: emptyMap()
|
||||
|
||||
// To fill empty lists when AniList does not return them
|
||||
val baseMap =
|
||||
AniListStatusType.values().filter { it.value >= 0 }.associate {
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
}
|
||||
}?.flatten() ?: emptyList()
|
||||
|
||||
return SyncAPI.LibraryMetadata(
|
||||
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -695,7 +700,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
startedAt { year month day }
|
||||
updatedAt
|
||||
progress
|
||||
score
|
||||
score (format: POINT_100)
|
||||
private
|
||||
media
|
||||
{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.lagradost.cloudstream3.AcraApplication
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
|
@ -68,20 +67,21 @@ class LocalList : SyncAPI {
|
|||
}?.distinctBy { it.first } ?: return null
|
||||
|
||||
val list = ioWork {
|
||||
watchStatusIds.mapNotNull {
|
||||
getBookmarkedData(it.first)?.toLibraryItem(it.second)
|
||||
watchStatusIds.groupBy {
|
||||
it.second.stringRes
|
||||
}.mapValues { group ->
|
||||
group.value.mapNotNull {
|
||||
getBookmarkedData(it.first)?.toLibraryItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SyncAPI.LibraryMetadata(
|
||||
WatchType.values().mapNotNull {
|
||||
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
|
||||
// None is not something to display
|
||||
if (it == WatchType.NONE) return@mapNotNull null
|
||||
|
||||
// Dirty hack for context!
|
||||
txt(it.stringRes).asStringNull(AcraApplication.context)
|
||||
},
|
||||
list
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
}
|
||||
return SyncAPI.LibraryMetadata(
|
||||
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.util.Base64
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
|
@ -15,6 +16,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager
|
|||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
|
@ -257,6 +259,32 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
const val MAL_UNIXTIME_KEY: String = "mal_unixtime" // When token expires
|
||||
const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token
|
||||
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
|
||||
|
||||
fun convertToStatus(string: String): MalStatusType {
|
||||
return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
|
||||
}
|
||||
|
||||
enum class MalStatusType(var value: Int, @StringRes val stringRes: Int) {
|
||||
Watching(0, R.string.type_watching),
|
||||
Completed(1, R.string.type_completed),
|
||||
OnHold(2, R.string.type_on_hold),
|
||||
Dropped(3, R.string.type_dropped),
|
||||
PlanToWatch(4, R.string.type_plan_to_watch),
|
||||
None(-1, R.string.type_none)
|
||||
}
|
||||
|
||||
private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
return when (inp) {
|
||||
-1 -> MalStatusType.None
|
||||
0 -> MalStatusType.Watching
|
||||
1 -> MalStatusType.Completed
|
||||
2 -> MalStatusType.OnHold
|
||||
3 -> MalStatusType.Dropped
|
||||
4 -> MalStatusType.PlanToWatch
|
||||
5 -> MalStatusType.Watching
|
||||
else -> MalStatusType.None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun handleRedirect(url: String): Boolean {
|
||||
|
@ -394,10 +422,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
this.node.title,
|
||||
"https://myanimelist.net/anime/${this.node.id}/",
|
||||
this.node.id.toString(),
|
||||
this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE",
|
||||
this.list_status?.num_episodes_watched,
|
||||
this.node.num_episodes,
|
||||
this.list_status?.score,
|
||||
this.list_status?.score?.times(10),
|
||||
"MAL",
|
||||
TvType.Anime,
|
||||
this.node.main_picture?.large ?: this.node.main_picture?.medium,
|
||||
|
@ -448,9 +475,20 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
|
||||
val list = getMalAnimeListSmart()?.groupBy {
|
||||
convertToStatus(it.list_status?.status ?: "").stringRes
|
||||
}?.mapValues { group ->
|
||||
group.value.map { it.toLibraryItem() }
|
||||
} ?: emptyMap()
|
||||
|
||||
// To fill empty lists when MAL does not return them
|
||||
val baseMap =
|
||||
MalStatusType.values().filter { it.value >= 0 }.associate {
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
}
|
||||
|
||||
return SyncAPI.LibraryMetadata(
|
||||
emptyList(),
|
||||
getMalAnimeListSmart()?.map { it.toLibraryItem() } ?: emptyList()
|
||||
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -469,10 +507,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return fullList.toTypedArray()
|
||||
}
|
||||
|
||||
fun convertToStatus(string: String): MalStatusType {
|
||||
return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
|
||||
}
|
||||
|
||||
private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? {
|
||||
val user = "@me"
|
||||
val auth = getAuth() ?: return null
|
||||
|
@ -586,28 +620,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return user
|
||||
}
|
||||
|
||||
enum class MalStatusType(var value: Int) {
|
||||
Watching(0),
|
||||
Completed(1),
|
||||
OnHold(2),
|
||||
Dropped(3),
|
||||
PlanToWatch(4),
|
||||
None(-1)
|
||||
}
|
||||
|
||||
private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||
return when (inp) {
|
||||
-1 -> MalStatusType.None
|
||||
0 -> MalStatusType.Watching
|
||||
1 -> MalStatusType.Completed
|
||||
2 -> MalStatusType.OnHold
|
||||
3 -> MalStatusType.Dropped
|
||||
4 -> MalStatusType.PlanToWatch
|
||||
5 -> MalStatusType.Watching
|
||||
else -> MalStatusType.None
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setScoreRequest(
|
||||
id: Int,
|
||||
status: MalStatusType? = null,
|
||||
|
|
|
@ -297,7 +297,7 @@ class LibraryFragment : Fragment() {
|
|||
library_tab_layout,
|
||||
viewpager,
|
||||
) { tab, position ->
|
||||
tab.text = pages.getOrNull(position)?.title
|
||||
tab.text = pages.getOrNull(position)?.title?.asStringNull(context)
|
||||
}.attach()
|
||||
loading_indicator?.hide()
|
||||
}
|
||||
|
|
|
@ -92,18 +92,13 @@ class LibraryViewModel : ViewModel() {
|
|||
|
||||
repo.requireLibraryRefresh = false
|
||||
|
||||
val listSubset = library.allLibraryItems.groupBy { it.listName }
|
||||
val allLists =
|
||||
library.allListNames.associateWith { emptyList<SyncAPI.LibraryItem>() }
|
||||
|
||||
val filledLists = allLists + listSubset
|
||||
|
||||
val pages = filledLists.map {
|
||||
val pages = library.allLibraryLists.map {
|
||||
SyncAPI.Page(
|
||||
it.key,
|
||||
it.value
|
||||
it.name,
|
||||
it.items
|
||||
)
|
||||
}
|
||||
|
||||
_pages.postValue(Resource.Success(pages))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,10 @@ class PageAdapter(
|
|||
return ColorUtils.calculateLuminance(color) < 0.5
|
||||
}
|
||||
|
||||
fun getDifferentColor(color: Int, ratio : Float = 0.7f) : Int {
|
||||
return if(isDark(color)) {
|
||||
fun getDifferentColor(color: Int, ratio: Float = 0.7f): Int {
|
||||
return if (isDark(color)) {
|
||||
ColorUtils.blendARGB(color, Color.WHITE, ratio)
|
||||
} else{
|
||||
} else {
|
||||
ColorUtils.blendARGB(color, Color.BLACK, ratio)
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class PageAdapter(
|
|||
itemView,
|
||||
colorCallback = { palette ->
|
||||
AcraApplication.context?.let { ctx ->
|
||||
val defColor = ContextCompat.getColor(ctx,R.color.ratingColorBg)
|
||||
val defColor = ContextCompat.getColor(ctx, R.color.ratingColorBg)
|
||||
var bg = palette.getDarkVibrantColor(defColor)
|
||||
if (bg == defColor) {
|
||||
bg = palette.getDarkMutedColor(defColor)
|
||||
|
@ -83,11 +83,12 @@ class PageAdapter(
|
|||
bg = palette.getVibrantColor(defColor)
|
||||
}
|
||||
|
||||
val fg = getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor))
|
||||
val fg =
|
||||
getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor))
|
||||
itemView.text_rating.apply {
|
||||
setTextColor(ColorStateList.valueOf(fg))
|
||||
}
|
||||
itemView.text_rating_holder?.backgroundTintList =ColorStateList.valueOf(bg)
|
||||
itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg)
|
||||
itemView.watchProgress?.apply {
|
||||
progressTintList = ColorStateList.valueOf(fg)
|
||||
progressBackgroundTintList = ColorStateList.valueOf(bg)
|
||||
|
@ -118,7 +119,11 @@ class PageAdapter(
|
|||
val showRating = (item.personalRating ?: 0) != 0
|
||||
itemView.text_rating_holder.isVisible = showRating
|
||||
if (showRating) {
|
||||
itemView.text_rating.text = "★ ${item.personalRating.toString()}"
|
||||
// We want to show 8.5 but not 8.0 hence the replace
|
||||
val rating = ((item.personalRating ?: 0).toDouble() / 10).toString()
|
||||
.replace(".0", "")
|
||||
|
||||
itemView.text_rating.text = "★ $rating"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,12 +55,11 @@ object DataStoreHelper {
|
|||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
||||
) : SearchResponse {
|
||||
fun toLibraryItem(state: WatchType): SyncAPI.LibraryItem {
|
||||
fun toLibraryItem(): SyncAPI.LibraryItem {
|
||||
return SyncAPI.LibraryItem(
|
||||
name,
|
||||
url,
|
||||
url,
|
||||
state.name.lowercase().capitalize(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/background_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="2dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:elevation="10dp"
|
||||
|
|
Loading…
Reference in a new issue