sync fixes + UI

This commit is contained in:
LagradOst 2022-04-03 03:13:02 +02:00
parent a933aa8493
commit 4447196ebc
13 changed files with 556 additions and 372 deletions

View file

@ -693,13 +693,13 @@ interface LoadResponse {
val url: String
val apiName: String
val type: TvType
val posterUrl: String?
var posterUrl: String?
val year: Int?
val plot: String?
val rating: Int? // 1-1000
val tags: List<String>?
var plot: String?
var rating: Int? // 1-1000
var tags: List<String>?
var duration: Int? // in minutes
val trailerUrl: String?
var trailerUrl: String?
var recommendations: List<SearchResponse>?
var actors: List<ActorData>?
var comingSoon: Boolean

View file

@ -73,6 +73,8 @@ interface SyncAPI : OAuth2API {
var synonyms: List<String>? = null,
var trailerUrl: String? = null,
var isAdult : Boolean? = null,
var posterUrl: String? = null,
var backgroundPosterUrl : String? = null,
/** In unixtime */
var startDate: Long? = null,

View file

@ -8,6 +8,7 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall
class SyncRepo(private val repo: SyncAPI) {
val idPrefix = repo.idPrefix
val name = repo.name
val icon = repo.icon
suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> {
return safeApiCall { repo.score(id, status) }

View file

@ -9,6 +9,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
@ -20,6 +21,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import java.net.URL
@ -86,7 +88,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override suspend fun getResult(id: String): SyncAPI.SyncResult? {
val internalId = id.toIntOrNull() ?: return null
val season = getSeason(internalId)?.data?.Media ?: return null
val season = getSeason(internalId).data?.Media ?: throw ErrorLoadingException("No media")
return SyncAPI.SyncResult(
season.id.toString(),
@ -100,7 +102,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
synonyms = season.synonyms,
isAdult = season.isAdult,
totalEpisodes = season.episodes,
//synopsis = season.
synopsis = season.description,
//TODO REST
)
}
@ -295,28 +298,42 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
}
private suspend fun getSeason(id: Int): SeasonResponse? {
private suspend fun getSeason(id: Int): SeasonResponse {
val q = """
query (${'$'}id: Int = $id) {
Media (id: ${'$'}id, type: ANIME) {
id
idMal
coverImage
coverImage {
extraLarge
large
medium
color
}
duration
episodes
genres
synonyms
averageScore
isAdult
trailer
description(asHtml: false)
trailer {
id
site
thumbnail
}
relations {
edges {
id
relationType(version: 2)
node {
id
coverImage
coverImage {
extraLarge
large
medium
color
}
}
}
}
@ -328,19 +345,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
"""
val data = app.post(
"https://graphql.anilist.co",
data = mapOf("query" to q),
cacheTime = 0,
).text
if (data == "") return null
return try {
mapper.readValue(data)
} catch (e: Exception) {
logError(e)
null
}
return tryParseJson(data) ?: throw ErrorLoadingException("Error parsing $data")
}
}
@ -661,7 +672,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val seasons = mutableListOf<SeasonResponse?>()
suspend fun getSeasonRecursive(id: Int) {
val season = getSeason(id)
if (season != null) {
seasons.add(season)
if (season.data?.Media?.format?.startsWith("TV") == true) {
season.data.Media.relations?.edges?.forEach {
@ -674,7 +684,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
}
}
getSeasonRecursive(id)
return seasons.toList()
}
@ -701,7 +710,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("averageScore") val averageScore: Int?,
@JsonProperty("isAdult") val isAdult: Boolean?,
@JsonProperty("trailer") val trailer: MediaTrailer?,
@JsonProperty("description") val description: String?,
)
data class MediaTrailer(

View file

@ -0,0 +1,82 @@
package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
/*
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val newConvertView = convertView ?: run {
val mInflater = context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
mInflater.inflate(resource, null)
}
getItem(position)?.let { (newConvertView as? ImageView?)?.setImageResource(it) }
return newConvertView
}
}*/
class ImageAdapter(
val layout: Int,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val images: MutableList<Int> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ImageViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ImageViewHolder -> {
holder.bind(images[position])
}
}
}
override fun getItemCount(): Int {
return images.size
}
override fun getItemId(position: Int): Long {
return images[position].toLong()
}
fun updateList(newList: List<Int>) {
val diffResult = DiffUtil.calculateDiff(
DiffCallback(this.images, newList)
)
images.clear()
images.addAll(newList)
diffResult.dispatchUpdatesTo(this)
}
class ImageViewHolder
constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bind(img: Int) {
(itemView as? ImageView?)?.setImageResource(img)
}
}
}
class DiffCallback<T>(private val oldList: List<T>, private val newList: List<T>) :
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
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]
}

View file

@ -636,7 +636,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
val apiName = arguments?.getString("apiName") ?: return
startAction = arguments?.getInt("startAction") ?: START_ACTION_NORMAL
startValue = arguments?.getInt("startValue") ?: START_VALUE_NORMAL
syncModel.addFromUrl(url)
val api = getApiFromName(apiName)
@ -1198,17 +1197,26 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
}
val imgAdapter = ImageAdapter(R.layout.result_mini_image)
result_mini_sync?.adapter = imgAdapter
observe(syncModel.synced) { list ->
result_sync_names?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced }
result_mini_sync?.isVisible = newList.isNotEmpty()
(result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.map { it.icon })
}
var currentSyncProgress = 0
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
result_sync_episodes?.max = (d.totalEpisodes ?: 0) * 1000
result_sync_episodes?.progress = currentSyncProgress * 1000
normalSafeApiCall {
val ctx = result_sync_max_episodes?.context
result_sync_max_episodes?.text =
@ -1218,6 +1226,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
ctx?.getString(R.string.sync_total_episodes_none)
}
}
viewModel.setMeta(d)
}
is Resource.Loading -> {
result_sync_max_episodes?.text =
@ -1249,6 +1258,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
result_sync_rating?.value = d.score?.toFloat() ?: 0.0f
result_sync_check?.setItemChecked(d.status + 1, true)
val watchedEpisodes = d.watchedEpisodes ?: 0
currentSyncProgress = watchedEpisodes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result_sync_episodes?.setProgress(watchedEpisodes * 1000, true)
} else {
@ -1447,11 +1457,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
currentId = it
}
observe(viewModel.resultResponse) { data ->
observe(viewModel.result) { data ->
when (data) {
is Resource.Success -> {
val d = data.value
if (d is LoadResponse) {
if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime
result_episode_loading.isVisible = false
}
@ -1533,6 +1542,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setAniListSync(d.anilistId)
) {
syncModel.updateMetaAndUser()
} else {
syncModel.addFromUrl(d.url)
}
}
@ -1799,7 +1810,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
result_meta_type?.text = it
}
when (d) {
is AnimeLoadResponse -> {
@ -1811,9 +1821,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
else -> result_title.text = d.name
}
} else {
updateVisStatus(1)
}
}
is Resource.Failure -> {
result_error_text.text = url?.plus("\n") + data.errorString
@ -1873,7 +1880,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
it.context?.openBrowser(tempUrl)
}
if (restart || viewModel.resultResponse.value == null) {
if (restart || viewModel.result.value == null) {
//viewModel.clear()
viewModel.load(tempUrl, apiName, showFillers)
}

View file

@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.ui.result
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.IGenerator
@ -43,7 +44,7 @@ class ResultViewModel : ViewModel() {
private var repo: APIRepository? = null
private var generator: IGenerator? = null
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
private val _resultResponse: MutableLiveData<Resource<LoadResponse>> = MutableLiveData()
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
private val episodeById: MutableLiveData<HashMap<Int, Int>> =
MutableLiveData() // lookup by ID to get Index
@ -55,7 +56,8 @@ class ResultViewModel : ViewModel() {
private val selectedRangeInt: MutableLiveData<Int> = MutableLiveData()
val rangeOptions: LiveData<List<String>> = _rangeOptions
val resultResponse: LiveData<Resource<Any?>> get() = _resultResponse
val result: LiveData<Resource<LoadResponse>> get() = _resultResponse
val episodes: LiveData<List<ResultEpisode>> get() = _episodes
val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes
val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount
@ -106,6 +108,41 @@ class ResultViewModel : ViewModel() {
}
}
companion object {
const val TAG = "RVM"
}
var lastMeta: SyncAPI.SyncResult? = null
private fun applyMeta(resp: LoadResponse, meta: SyncAPI.SyncResult?): LoadResponse {
if (meta == null) return resp
lastMeta = meta
return resp.apply {
Log.i(TAG, "applyMeta")
duration = duration ?: meta.duration
rating = rating ?: meta.publicScore
tags = tags ?: meta.genres
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
trailerUrl = trailerUrl ?: meta.trailerUrl
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
actors = actors ?: meta.actors?.map {
ActorData(
Actor(
name = it.name,
image = it.posterUrl
)
)
}
}
}
fun setMeta(meta: SyncAPI.SyncResult) {
Log.i(TAG, "setMeta")
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
_resultResponse.postValue(Resource.Success(applyMeta(resp, meta)))
}
}
private fun loadWatchStatus(localId: Int? = null) {
val currentId = localId ?: id.value ?: return
val currentWatch = getResultWatchState(currentId)
@ -289,7 +326,7 @@ class ResultViewModel : ViewModel() {
when (data) {
is Resource.Success -> {
val d = data.value
val d = applyMeta(data.value, lastMeta)
page.postValue(d)
val mainId = d.getId()
id.postValue(mainId)

View file

@ -18,6 +18,7 @@ data class CurrentSynced(
val idPrefix: String,
val isSynced: Boolean,
val hasAccount: Boolean,
val icon : Int,
)
class SyncViewModel : ViewModel() {
@ -48,7 +49,8 @@ class SyncViewModel : ViewModel() {
it.name,
it.idPrefix,
syncIds.containsKey(it.idPrefix),
it.hasAccount()
it.hasAccount(),
it.icon,
)
}
}
@ -67,8 +69,13 @@ class SyncViewModel : ViewModel() {
updateSynced()
}
var hasAddedFromUrl : HashSet<String> = hashSetOf()
fun addFromUrl(url: String?) = viewModelScope.launch {
if(url == null || hasAddedFromUrl.contains(url)) return@launch
SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) ->
hasAddedFromUrl.add(url)
setMalId(malId)
setAniListId(aniListId)
if (malId != null || aniListId != null) {

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.utils
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.app
@ -14,6 +15,8 @@ object SyncUtil {
Regex("""(twist\.moe)/a/([^/?]*)"""),
)
private const val TAG = "SNC"
private const val GOGOANIME = "Gogoanime"
private const val NINE_ANIME = "9anime"
private const val TWIST_MOE = "Twistmoe"
@ -28,6 +31,7 @@ object SyncUtil {
suspend fun getIdsFromUrl(url: String?): Pair<String?, String?>? {
if (url == null) return null
Log.i(TAG, "getIdsFromUrl $url")
for (regex in regexs) {
regex.find(url)?.let { match ->
@ -51,6 +55,7 @@ object SyncUtil {
slug: String,
site: String = "GogoanimeGogoanime"
): Pair<String?, String?>? {
Log.i(TAG, "getIdsFromSlug $slug $site")
try {
//Gogoanime, Twistmoe and 9anime
val url =

View file

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:width="20dp"
android:height="20dp"
android:viewportWidth="172"
android:viewportHeight="172"
android:tint="?attr/white"

View file

@ -375,6 +375,7 @@
</LinearLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/result_bookmark_button"
style="@style/BlackButton"

View file

@ -12,7 +12,12 @@
android:paddingEnd="@dimen/result_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:nextFocusDown="@id/result_bookmark_button"
android:nextFocusRight="@id/result_share"
@ -28,6 +33,24 @@
android:src="@drawable/ic_baseline_arrow_back_24"
android:contentDescription="@string/go_back"
app:tint="?attr/white" />
<androidx.recyclerview.widget.RecyclerView
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:id="@+id/result_mini_sync"
android:layout_width="match_parent"
android:descendantFocusability="afterDescendants"
android:layout_height="wrap_content"
android:fadingEdge="horizontal"
android:focusableInTouchMode="false"
android:focusable="false"
android:layout_gravity="center"
android:orientation="horizontal"
android:paddingTop="5dp"
android:requiresFadingEdge="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="@layout/result_mini_image" />
</LinearLayout>
<LinearLayout
android:gravity="end"

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_gravity="center"
tools:src="@drawable/ic_anilist_icon"
app:tint="?attr/white">
</ImageView>