From 4447196ebc1fa576017230ee4f550d77355affa6 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sun, 3 Apr 2022 03:13:02 +0200 Subject: [PATCH] sync fixes + UI --- .../com/lagradost/cloudstream3/MainAPI.kt | 10 +- .../cloudstream3/syncproviders/SyncAPI.kt | 2 + .../cloudstream3/syncproviders/SyncRepo.kt | 1 + .../syncproviders/providers/AniListApi.kt | 61 +- .../cloudstream3/ui/result/ImageAdapter.kt | 82 +++ .../cloudstream3/ui/result/ResultFragment.kt | 647 +++++++++--------- .../cloudstream3/ui/result/ResultViewModel.kt | 45 +- .../cloudstream3/ui/result/SyncViewModel.kt | 9 +- .../lagradost/cloudstream3/utils/SyncUtil.kt | 5 + app/src/main/res/drawable/ic_anilist_icon.xml | 4 +- app/src/main/res/layout/fragment_result.xml | 1 + .../main/res/layout/fragment_result_swipe.xml | 51 +- app/src/main/res/layout/result_mini_image.xml | 10 + 13 files changed, 556 insertions(+), 372 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt create mode 100644 app/src/main/res/layout/result_mini_image.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 3b27e9c2..901fabbc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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? + var plot: String? + var rating: Int? // 1-1000 + var tags: List? var duration: Int? // in minutes - val trailerUrl: String? + var trailerUrl: String? var recommendations: List? var actors: List? var comingSoon: Boolean diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index 6be3f8db..8ecaae56 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -73,6 +73,8 @@ interface SyncAPI : OAuth2API { var synonyms: List? = null, var trailerUrl: String? = null, var isAdult : Boolean? = null, + var posterUrl: String? = null, + var backgroundPosterUrl : String? = null, /** In unixtime */ var startDate: Long? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt index c40ab3d6..bbdc061d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -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 { return safeApiCall { repo.score(id, status) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 15137a34..2d92e1bb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -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() 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?, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt new file mode 100644 index 00000000..0be6d644 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt @@ -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(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() { + private val images: MutableList = 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) { + 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(private val oldList: List, private val newList: List) : + 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] +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index df3baf9b..4b736bbb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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(R.id.imgPoster) - ?.apply { - setImageBitmap(bitmap) - setOnClickListener { - sourceDialog.dismissSafe() - } + sourceDialog.findViewById(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(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(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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt index 3948c2eb..8f03ba43 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel.kt @@ -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> = MutableLiveData() + private val _resultResponse: MutableLiveData> = MutableLiveData() private val _episodes: MutableLiveData> = MutableLiveData() private val episodeById: MutableLiveData> = MutableLiveData() // lookup by ID to get Index @@ -55,7 +56,8 @@ class ResultViewModel : ViewModel() { private val selectedRangeInt: MutableLiveData = MutableLiveData() val rangeOptions: LiveData> = _rangeOptions - val resultResponse: LiveData> get() = _resultResponse + val result: LiveData> get() = _resultResponse + val episodes: LiveData> get() = _episodes val publicEpisodes: LiveData>> get() = _publicEpisodes val publicEpisodesCount: LiveData 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?)?.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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt index de33e465..a01d7635 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt @@ -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 = 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) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index a2278853..1ebef068 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -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? { 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? { + Log.i(TAG, "getIdsFromSlug $slug $site") try { //Gogoanime, Twistmoe and 9anime val url = diff --git a/app/src/main/res/drawable/ic_anilist_icon.xml b/app/src/main/res/drawable/ic_anilist_icon.xml index 46b3af2b..cd164f61 100644 --- a/app/src/main/res/drawable/ic_anilist_icon.xml +++ b/app/src/main/res/drawable/ic_anilist_icon.xml @@ -1,6 +1,6 @@ + + + + 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" /> + + + + \ No newline at end of file