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,15 +672,13 @@ 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 {
if (it.node?.format != null) {
if (it.relationType == "SEQUEL" && it.node.format.startsWith("TV")) {
getSeasonRecursive(it.node.id)
return@forEach
}
seasons.add(season)
if (season.data?.Media?.format?.startsWith("TV") == true) {
season.data.Media.relations?.edges?.forEach {
if (it.node?.format != null) {
if (it.relationType == "SEQUEL" && it.node.format.startsWith("TV")) {
getSeasonRecursive(it.node.id)
return@forEach
}
}
}
@ -701,8 +710,8 @@ 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(
@JsonProperty("id") val id: String?,

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,372 +1457,369 @@ 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
if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime
result_episode_loading.isVisible = false
}
updateVisStatus(2)
result_vpn?.text = when (api.vpnStatus) {
VPNStatus.MightBeNeeded -> getString(R.string.vpn_might_be_needed)
VPNStatus.Torrent -> getString(R.string.vpn_torrent)
else -> ""
}
result_vpn?.isGone = api.vpnStatus == VPNStatus.None
result_info?.text = when (api.providerType) {
ProviderType.MetaProvider -> getString(R.string.provider_info_meta)
else -> ""
}
result_info?.isVisible = api.providerType == ProviderType.MetaProvider
if (d.type.isEpisodeBased()) {
val ep = d as? TvSeriesLoadResponse
val epCount = ep?.episodes?.size ?: 1
if (epCount < 1) {
result_info?.text = getString(R.string.no_episodes_found)
result_info?.isVisible = true
}
}
updateVisStatus(2)
currentHeaderName = d.name
currentType = d.type
result_vpn?.text = when (api.vpnStatus) {
VPNStatus.MightBeNeeded -> getString(R.string.vpn_might_be_needed)
VPNStatus.Torrent -> getString(R.string.vpn_torrent)
else -> ""
currentPoster = d.posterUrl
currentIsMovie = !d.isEpisodeBased()
result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW)
i.data = Uri.parse(d.url)
try {
startActivity(i)
} catch (e: Exception) {
logError(e)
}
result_vpn?.isGone = api.vpnStatus == VPNStatus.None
}
result_info?.text = when (api.providerType) {
ProviderType.MetaProvider -> getString(R.string.provider_info_meta)
else -> ""
result_search?.setOnClickListener {
QuickSearchFragment.pushSearch(activity, d.name)
}
result_share?.setOnClickListener {
try {
val i = Intent(ACTION_SEND)
i.type = "text/plain"
i.putExtra(EXTRA_SUBJECT, d.name)
i.putExtra(EXTRA_TEXT, d.url)
startActivity(createChooser(i, d.name))
} catch (e: Exception) {
logError(e)
}
result_info?.isVisible = api.providerType == ProviderType.MetaProvider
}
if (d.type.isEpisodeBased()) {
val ep = d as? TvSeriesLoadResponse
val epCount = ep?.episodes?.size ?: 1
if (epCount < 1) {
result_info?.text = getString(R.string.no_episodes_found)
result_info?.isVisible = true
val showStatus = when (d) {
is TvSeriesLoadResponse -> d.showStatus
is AnimeLoadResponse -> d.showStatus
else -> null
}
setShow(showStatus)
setDuration(d.duration)
setYear(d.year)
setRating(d.rating)
setRecommendations(d.recommendations)
setActors(d.actors)
if (SettingsFragment.accountEnabled)
if (d is AnimeLoadResponse) {
if (
setMalSync(d.malId)
||
setAniListSync(d.anilistId)
) {
syncModel.updateMetaAndUser()
} else {
syncModel.addFromUrl(d.url)
}
}
currentHeaderName = d.name
currentType = d.type
result_meta_site?.text = d.apiName
currentPoster = d.posterUrl
currentIsMovie = !d.isEpisodeBased()
result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW)
i.data = Uri.parse(d.url)
val posterImageLink = d.posterUrl
if (!posterImageLink.isNullOrEmpty()) {
result_poster?.setImage(posterImageLink)
result_poster_blur?.setImageBlur(posterImageLink, 10, 3)
//Full screen view of Poster image
result_poster_holder?.setOnClickListener {
try {
startActivity(i)
} catch (e: Exception) {
logError(e)
}
}
context?.let { ctx ->
val bitmap = result_poster.drawable.toBitmap()
val sourceBuilder = AlertDialog.Builder(ctx)
sourceBuilder.setView(R.layout.result_poster)
result_search?.setOnClickListener {
QuickSearchFragment.pushSearch(activity, d.name)
}
val sourceDialog = sourceBuilder.create()
sourceDialog.show()
result_share?.setOnClickListener {
try {
val i = Intent(ACTION_SEND)
i.type = "text/plain"
i.putExtra(EXTRA_SUBJECT, d.name)
i.putExtra(EXTRA_TEXT, d.url)
startActivity(createChooser(i, d.name))
} catch (e: Exception) {
logError(e)
}
}
val showStatus = when (d) {
is TvSeriesLoadResponse -> d.showStatus
is AnimeLoadResponse -> d.showStatus
else -> null
}
setShow(showStatus)
setDuration(d.duration)
setYear(d.year)
setRating(d.rating)
setRecommendations(d.recommendations)
setActors(d.actors)
if (SettingsFragment.accountEnabled)
if (d is AnimeLoadResponse) {
if (
setMalSync(d.malId)
||
setAniListSync(d.anilistId)
) {
syncModel.updateMetaAndUser()
}
}
result_meta_site?.text = d.apiName
val posterImageLink = d.posterUrl
if (!posterImageLink.isNullOrEmpty()) {
result_poster?.setImage(posterImageLink)
result_poster_blur?.setImageBlur(posterImageLink, 10, 3)
//Full screen view of Poster image
result_poster_holder?.setOnClickListener {
try {
context?.let { ctx ->
val bitmap = result_poster.drawable.toBitmap()
val sourceBuilder = AlertDialog.Builder(ctx)
sourceBuilder.setView(R.layout.result_poster)
val sourceDialog = sourceBuilder.create()
sourceDialog.show()
sourceDialog.findViewById<ImageView?>(R.id.imgPoster)
?.apply {
setImageBitmap(bitmap)
setOnClickListener {
sourceDialog.dismissSafe()
}
sourceDialog.findViewById<ImageView?>(R.id.imgPoster)
?.apply {
setImageBitmap(bitmap)
setOnClickListener {
sourceDialog.dismissSafe()
}
}
} catch (e: Exception) {
logError(e)
}
}
} catch (e: Exception) {
logError(e)
}
} else {
result_poster?.setImageResource(R.drawable.default_cover)
result_poster_blur?.setImageResource(R.drawable.default_cover)
}
} else {
result_poster?.setImageResource(R.drawable.default_cover)
result_poster_blur?.setImageResource(R.drawable.default_cover)
}
result_poster_holder?.visibility = VISIBLE
result_poster_holder?.visibility = VISIBLE
/*result_play_movie?.text =
if (d.type == TvType.Torrent) getString(R.string.play_torrent_button) else getString(
R.string.play_movie_button
)*/
//result_plot_header?.text =
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
if (!d.plot.isNullOrEmpty()) {
var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) {
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
}
result_descript.setOnClickListener {
val builder: AlertDialog.Builder =
AlertDialog.Builder(requireContext())
builder.setMessage(d.plot)
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
.show()
}
result_descript.text = syno
} else {
result_descript.text =
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
R.string.normal_no_plot
)
/*result_play_movie?.text =
if (d.type == TvType.Torrent) getString(R.string.play_torrent_button) else getString(
R.string.play_movie_button
)*/
//result_plot_header?.text =
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
if (!d.plot.isNullOrEmpty()) {
var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) {
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
}
result_descript.setOnClickListener {
val builder: AlertDialog.Builder =
AlertDialog.Builder(requireContext())
builder.setMessage(d.plot)
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
.show()
}
result_descript.text = syno
} else {
result_descript.text =
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
R.string.normal_no_plot
)
}
result_tag?.removeAllViews()
result_tag?.removeAllViews()
//result_tag_holder?.visibility = GONE
// result_status.visibility = GONE
d.comingSoon.let { soon ->
result_coming_soon?.isVisible = soon
result_data_holder?.isGone = soon
}
val tags = d.tags
if (tags.isNullOrEmpty()) {
//result_tag_holder?.visibility = GONE
// result_status.visibility = GONE
} else {
//result_tag_holder?.visibility = VISIBLE
d.comingSoon.let { soon ->
result_coming_soon?.isVisible = soon
result_data_holder?.isGone = soon
for ((index, tag) in tags.withIndex()) {
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
btt.text = tag
result_tag?.addView(viewBtt, index)
}
}
if (d.type.isMovieType()) {
val hasDownloadSupport = api.hasDownloadSupport
lateFixDownloadButton(true)
result_play_movie?.setOnClickListener {
val card =
currentEpisodes?.firstOrNull() ?: return@setOnClickListener
handleAction(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
val tags = d.tags
if (tags.isNullOrEmpty()) {
//result_tag_holder?.visibility = GONE
} else {
//result_tag_holder?.visibility = VISIBLE
for ((index, tag) in tags.withIndex()) {
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
btt.text = tag
result_tag?.addView(viewBtt, index)
}
result_play_movie?.setOnLongClickListener {
val card = currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener true
handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
if (d.type.isMovieType()) {
val hasDownloadSupport = api.hasDownloadSupport
lateFixDownloadButton(true)
result_play_movie?.setOnClickListener {
val card =
currentEpisodes?.firstOrNull() ?: return@setOnClickListener
handleAction(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
result_play_movie?.setOnLongClickListener {
val card = currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener true
handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
result_download_movie?.setOnLongClickListener {
val card = currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener true
handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
result_download_movie?.setOnLongClickListener {
val card = currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener true
handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
// result_options.setOnClickListener {
// val card = currentEpisodes?.first() ?: return@setOnClickListener
// handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
// }
result_download_movie?.visibility =
if (hasDownloadSupport) VISIBLE else GONE
if (hasDownloadSupport) {
val localId = d.getId()
val file =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
requireContext(),
localId
)
downloadButton?.dispose()
downloadButton = EasyDownloadButton()
downloadButton?.setUpMoreButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_movie_download_icon,
result_movie_download_text,
result_movie_download_text_precentage,
result_download_movie,
true,
VideoDownloadHelper.DownloadEpisodeCached(
d.name,
d.posterUrl,
0,
null,
localId,
localId,
d.rating,
d.plot,
System.currentTimeMillis(),
)
) { downloadClickEvent ->
if (downloadClickEvent.action == DOWNLOAD_ACTION_DOWNLOAD) {
currentEpisodes?.firstOrNull()?.let { episode ->
handleAction(
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
ResultEpisode(
d.name,
d.name,
null,
0,
null,
episode.data,
d.apiName,
localId,
0,
0L,
0L,
null,
null,
null,
d.type,
localId,
)
result_download_movie?.visibility =
if (hasDownloadSupport) VISIBLE else GONE
if (hasDownloadSupport) {
val localId = d.getId()
val file =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
requireContext(),
localId
)
downloadButton?.dispose()
downloadButton = EasyDownloadButton()
downloadButton?.setUpMoreButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_movie_download_icon,
result_movie_download_text,
result_movie_download_text_precentage,
result_download_movie,
true,
VideoDownloadHelper.DownloadEpisodeCached(
d.name,
d.posterUrl,
0,
null,
localId,
localId,
d.rating,
d.plot,
System.currentTimeMillis(),
)
) { downloadClickEvent ->
if (downloadClickEvent.action == DOWNLOAD_ACTION_DOWNLOAD) {
currentEpisodes?.firstOrNull()?.let { episode ->
handleAction(
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
ResultEpisode(
d.name,
d.name,
null,
0,
null,
episode.data,
d.apiName,
localId,
0,
0L,
0L,
null,
null,
null,
d.type,
localId,
)
)
}
} else {
handleDownloadClick(
activity,
currentHeaderName,
downloadClickEvent
)
}
}
result_download_movie?.setOnLongClickListener {
val card =
currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener false
handleAction(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
return@setOnLongClickListener true
}
/*downloadButton?.setUpMaterialButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_download_movie,
null, //result_movie_text_progress
VideoDownloadHelper.DownloadEpisodeCached(
d.name,
d.posterUrl,
0,
null,
localId,
localId,
d.rating,
d.plot,
System.currentTimeMillis(),
} else {
handleDownloadClick(
activity,
currentHeaderName,
downloadClickEvent
)
) { downloadClickEvent ->
if (downloadClickEvent.action == DOWNLOAD_ACTION_DOWNLOAD) {
currentEpisodes?.firstOrNull()?.let { episode ->
handleAction(
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
ResultEpisode(
d.name,
d.name,
null,
0,
null,
episode.data,
d.apiName,
localId,
0,
0L,
0L,
null,
null,
null,
d.type,
localId,
)
}
}
result_download_movie?.setOnLongClickListener {
val card =
currentEpisodes?.firstOrNull()
?: return@setOnLongClickListener false
handleAction(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
return@setOnLongClickListener true
}
/*downloadButton?.setUpMaterialButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_download_movie,
null, //result_movie_text_progress
VideoDownloadHelper.DownloadEpisodeCached(
d.name,
d.posterUrl,
0,
null,
localId,
localId,
d.rating,
d.plot,
System.currentTimeMillis(),
)
) { downloadClickEvent ->
if (downloadClickEvent.action == DOWNLOAD_ACTION_DOWNLOAD) {
currentEpisodes?.firstOrNull()?.let { episode ->
handleAction(
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
ResultEpisode(
d.name,
d.name,
null,
0,
null,
episode.data,
d.apiName,
localId,
0,
0L,
0L,
null,
null,
null,
d.type,
localId,
)
)
}
} else {
handleDownloadClick(
activity,
currentHeaderName,
downloadClickEvent
)
}
}*/
}
} else {
lateFixDownloadButton(false)
}
context?.getString(
when (d.type) {
TvType.TvSeries -> R.string.tv_series_singular
TvType.Anime -> R.string.anime_singular
TvType.OVA -> R.string.ova_singular
TvType.AnimeMovie -> R.string.movies_singular
TvType.Cartoon -> R.string.cartoons_singular
TvType.Documentary -> R.string.documentaries_singular
TvType.Movie -> R.string.movies_singular
TvType.Torrent -> R.string.torrent_singular
TvType.AsianDrama -> R.string.asian_drama_singular
}
)?.let {
result_meta_type?.text = it
}
when (d) {
is AnimeLoadResponse -> {
// val preferEnglish = true
//val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
val titleName = d.name
result_title.text = titleName
//result_toolbar.title = titleName
}
else -> result_title.text = d.name
} else {
handleDownloadClick(
activity,
currentHeaderName,
downloadClickEvent
)
}
}*/
}
} else {
updateVisStatus(1)
lateFixDownloadButton(false)
}
context?.getString(
when (d.type) {
TvType.TvSeries -> R.string.tv_series_singular
TvType.Anime -> R.string.anime_singular
TvType.OVA -> R.string.ova_singular
TvType.AnimeMovie -> R.string.movies_singular
TvType.Cartoon -> R.string.cartoons_singular
TvType.Documentary -> R.string.documentaries_singular
TvType.Movie -> R.string.movies_singular
TvType.Torrent -> R.string.torrent_singular
TvType.AsianDrama -> R.string.asian_drama_singular
}
)?.let {
result_meta_type?.text = it
}
when (d) {
is AnimeLoadResponse -> {
// val preferEnglish = true
//val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
val titleName = d.name
result_title.text = titleName
//result_toolbar.title = titleName
}
else -> result_title.text = d.name
}
}
is Resource.Failure -> {
@ -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,22 +12,45 @@
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"
android:background="?android:attr/selectableItemBackgroundBorderless"
<ImageView
android:nextFocusDown="@id/result_bookmark_button"
android:nextFocusRight="@id/result_share"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:id="@+id/result_back"
android:clickable="true"
android:focusable="true"
android:id="@+id/result_back"
android:clickable="true"
android:focusable="true"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical|start"
android:src="@drawable/ic_baseline_arrow_back_24"
android:contentDescription="@string/go_back"
app:tint="?attr/white" />
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical|start"
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>