Reworked backend to make list titles use UiText

This commit is contained in:
Blatzar 2023-01-25 19:20:53 +01:00
parent 9bc90438ec
commit 26320bb535
9 changed files with 106 additions and 89 deletions

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.syncproviders
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.library.ListSorting
import com.lagradost.cloudstream3.ui.result.UiText
import me.xdrop.fuzzywuzzy.FuzzySearch import me.xdrop.fuzzywuzzy.FuzzySearch
enum class SyncIdName { enum class SyncIdName {
@ -101,7 +102,7 @@ interface SyncAPI : OAuth2API {
data class Page( data class Page(
val title: String, var items: List<LibraryItem> val title: UiText, var items: List<LibraryItem>
) { ) {
fun sort(method: ListSorting?, query: String? = null) { fun sort(method: ListSorting?, query: String? = null) {
items = when (method) { items = when (method) {
@ -123,11 +124,12 @@ interface SyncAPI : OAuth2API {
} }
data class LibraryMetadata( data class LibraryMetadata(
/** List of all available pages, useful to show empty pages val allLibraryLists: List<LibraryList>
* 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 */ data class LibraryList(
val allLibraryItems: List<LibraryItem> val name: UiText,
val items: List<LibraryItem>
) )
data class LibraryItem( data class LibraryItem(
@ -135,7 +137,6 @@ interface SyncAPI : OAuth2API {
override val url: String, override val url: String,
/** Unique unchanging string used for data storage */ /** Unique unchanging string used for data storage */
val syncId: String, val syncId: String,
val listName: String,
val episodesCompleted: Int?, val episodesCompleted: Int?,
val episodesTotal: Int?, val episodesTotal: Int?,
/** Out of 100 */ /** Out of 100 */

View file

@ -1,9 +1,9 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.capitalize
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey 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.AuthAPI
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.SyncIdName 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.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
@ -316,14 +317,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
} }
// Changing names of these will show up in UI // Changing names of these will show up in UI
enum class AniListStatusType(var value: Int) { enum class AniListStatusType(var value: Int, @StringRes val stringRes: Int) {
Watching(0), Watching(0, R.string.type_watching),
Completed(1), Completed(1, R.string.type_completed),
Paused(2), Paused(2, R.string.type_on_hold),
Dropped(3), Dropped(3, R.string.type_dropped),
Planning(4), Planning(4, R.string.type_plan_to_watch),
ReWatching(5), ReWatching(5, R.string.type_re_watching),
None(-1) None(-1, R.string.none)
} }
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp } 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)) return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
} }
@ -609,7 +610,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("private") val private: Boolean, @JsonProperty("private") val private: Boolean,
@JsonProperty("media") val media: Media @JsonProperty("media") val media: Media
) { ) {
fun toLibraryItem(listName: String?): SyncAPI.LibraryItem? { fun toLibraryItem(): SyncAPI.LibraryItem {
return SyncAPI.LibraryItem( return SyncAPI.LibraryItem(
// English title first // English title first
this.media.title.english ?: this.media.title.romaji 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}/", "https://anilist.co/anime/${this.media.id}/",
this.media.id.toString(), this.media.id.toString(),
listName?.lowercase()?.capitalize() ?: return null,
this.progress, this.progress,
this.media.episodes, this.media.episodes,
this.score, this.score,
@ -665,15 +665,20 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
} }
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata { override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
return SyncAPI.LibraryMetadata( val list = getAniListAnimeListSmart()?.groupBy {
emptyList(), convertAniListStringToStatus(it.status ?: "").stringRes
getAniListAnimeListSmart()?.map { }?.mapValues { group ->
it.entries.mapNotNull { entry -> group.value.map { it.entries.map { entry -> entry.toLibraryItem() } }.flatten()
entry.toLibraryItem( } ?: emptyMap()
entry.status ?: it.status
) // 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 } startedAt { year month day }
updatedAt updatedAt
progress progress
score score (format: POINT_100)
private private
media media
{ {

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
@ -68,20 +67,21 @@ class LocalList : SyncAPI {
}?.distinctBy { it.first } ?: return null }?.distinctBy { it.first } ?: return null
val list = ioWork { val list = ioWork {
watchStatusIds.mapNotNull { watchStatusIds.groupBy {
getBookmarkedData(it.first)?.toLibraryItem(it.second) it.second.stringRes
}.mapValues { group ->
group.value.mapNotNull {
getBookmarkedData(it.first)?.toLibraryItem()
}
} }
} }
return SyncAPI.LibraryMetadata( val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
WatchType.values().mapNotNull {
// None is not something to display // None is not something to display
if (it == WatchType.NONE) return@mapNotNull null it.stringRes to emptyList<SyncAPI.LibraryItem>()
}
// Dirty hack for context! return SyncAPI.LibraryMetadata(
txt(it.stringRes).asStringNull(AcraApplication.context) (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }
},
list
) )
} }

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import android.util.Base64 import android.util.Base64
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey 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.AuthAPI
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.SyncIdName 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.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
@ -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_UNIXTIME_KEY: String = "mal_unixtime" // When token expires
const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api 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 { override suspend fun handleRedirect(url: String): Boolean {
@ -394,10 +422,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
this.node.title, this.node.title,
"https://myanimelist.net/anime/${this.node.id}/", "https://myanimelist.net/anime/${this.node.id}/",
this.node.id.toString(), this.node.id.toString(),
this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE",
this.list_status?.num_episodes_watched, this.list_status?.num_episodes_watched,
this.node.num_episodes, this.node.num_episodes,
this.list_status?.score, this.list_status?.score?.times(10),
"MAL", "MAL",
TvType.Anime, TvType.Anime,
this.node.main_picture?.large ?: this.node.main_picture?.medium, 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 { 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( return SyncAPI.LibraryMetadata(
emptyList(), (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }
getMalAnimeListSmart()?.map { it.toLibraryItem() } ?: emptyList()
) )
} }
@ -469,10 +507,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return fullList.toTypedArray() return fullList.toTypedArray()
} }
fun convertToStatus(string: String): MalStatusType {
return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
}
private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? { private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? {
val user = "@me" val user = "@me"
val auth = getAuth() ?: return null val auth = getAuth() ?: return null
@ -586,28 +620,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return user 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( private suspend fun setScoreRequest(
id: Int, id: Int,
status: MalStatusType? = null, status: MalStatusType? = null,

View file

@ -297,7 +297,7 @@ class LibraryFragment : Fragment() {
library_tab_layout, library_tab_layout,
viewpager, viewpager,
) { tab, position -> ) { tab, position ->
tab.text = pages.getOrNull(position)?.title tab.text = pages.getOrNull(position)?.title?.asStringNull(context)
}.attach() }.attach()
loading_indicator?.hide() loading_indicator?.hide()
} }

View file

@ -92,18 +92,13 @@ class LibraryViewModel : ViewModel() {
repo.requireLibraryRefresh = false repo.requireLibraryRefresh = false
val listSubset = library.allLibraryItems.groupBy { it.listName } val pages = library.allLibraryLists.map {
val allLists =
library.allListNames.associateWith { emptyList<SyncAPI.LibraryItem>() }
val filledLists = allLists + listSubset
val pages = filledLists.map {
SyncAPI.Page( SyncAPI.Page(
it.key, it.name,
it.value it.items
) )
} }
_pages.postValue(Resource.Success(pages)) _pages.postValue(Resource.Success(pages))
} }
} }

View file

@ -49,10 +49,10 @@ class PageAdapter(
return ColorUtils.calculateLuminance(color) < 0.5 return ColorUtils.calculateLuminance(color) < 0.5
} }
fun getDifferentColor(color: Int, ratio : Float = 0.7f) : Int { fun getDifferentColor(color: Int, ratio: Float = 0.7f): Int {
return if(isDark(color)) { return if (isDark(color)) {
ColorUtils.blendARGB(color, Color.WHITE, ratio) ColorUtils.blendARGB(color, Color.WHITE, ratio)
} else{ } else {
ColorUtils.blendARGB(color, Color.BLACK, ratio) ColorUtils.blendARGB(color, Color.BLACK, ratio)
} }
} }
@ -74,7 +74,7 @@ class PageAdapter(
itemView, itemView,
colorCallback = { palette -> colorCallback = { palette ->
AcraApplication.context?.let { ctx -> 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) var bg = palette.getDarkVibrantColor(defColor)
if (bg == defColor) { if (bg == defColor) {
bg = palette.getDarkMutedColor(defColor) bg = palette.getDarkMutedColor(defColor)
@ -83,11 +83,12 @@ class PageAdapter(
bg = palette.getVibrantColor(defColor) 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 { itemView.text_rating.apply {
setTextColor(ColorStateList.valueOf(fg)) setTextColor(ColorStateList.valueOf(fg))
} }
itemView.text_rating_holder?.backgroundTintList =ColorStateList.valueOf(bg) itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg)
itemView.watchProgress?.apply { itemView.watchProgress?.apply {
progressTintList = ColorStateList.valueOf(fg) progressTintList = ColorStateList.valueOf(fg)
progressBackgroundTintList = ColorStateList.valueOf(bg) progressBackgroundTintList = ColorStateList.valueOf(bg)
@ -118,7 +119,11 @@ class PageAdapter(
val showRating = (item.personalRating ?: 0) != 0 val showRating = (item.personalRating ?: 0) != 0
itemView.text_rating_holder.isVisible = showRating itemView.text_rating_holder.isVisible = showRating
if (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"
} }
} }
} }

View file

@ -55,12 +55,11 @@ object DataStoreHelper {
@JsonProperty("quality") override var quality: SearchQuality? = null, @JsonProperty("quality") override var quality: SearchQuality? = null,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null, @JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
) : SearchResponse { ) : SearchResponse {
fun toLibraryItem(state: WatchType): SyncAPI.LibraryItem { fun toLibraryItem(): SyncAPI.LibraryItem {
return SyncAPI.LibraryItem( return SyncAPI.LibraryItem(
name, name,
url, url,
url, url,
state.name.lowercase().capitalize(),
null, null,
null, null,
null, null,

View file

@ -13,7 +13,7 @@
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/background_card" android:id="@+id/background_card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_margin="2dp" android:layout_margin="2dp"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
android:elevation="10dp" android:elevation="10dp"