forked from recloudstream/cloudstream
		
	sync fixes + UI
This commit is contained in:
		
							parent
							
								
									a933aa8493
								
							
						
					
					
						commit
						4447196ebc
					
				
					 13 changed files with 556 additions and 372 deletions
				
			
		|  | @ -693,13 +693,13 @@ interface LoadResponse { | ||||||
|     val url: String |     val url: String | ||||||
|     val apiName: String |     val apiName: String | ||||||
|     val type: TvType |     val type: TvType | ||||||
|     val posterUrl: String? |     var posterUrl: String? | ||||||
|     val year: Int? |     val year: Int? | ||||||
|     val plot: String? |     var plot: String? | ||||||
|     val rating: Int? // 1-1000 |     var rating: Int? // 1-1000 | ||||||
|     val tags: List<String>? |     var tags: List<String>? | ||||||
|     var duration: Int? // in minutes |     var duration: Int? // in minutes | ||||||
|     val trailerUrl: String? |     var trailerUrl: String? | ||||||
|     var recommendations: List<SearchResponse>? |     var recommendations: List<SearchResponse>? | ||||||
|     var actors: List<ActorData>? |     var actors: List<ActorData>? | ||||||
|     var comingSoon: Boolean |     var comingSoon: Boolean | ||||||
|  |  | ||||||
|  | @ -73,6 +73,8 @@ interface SyncAPI : OAuth2API { | ||||||
|         var synonyms: List<String>? = null, |         var synonyms: List<String>? = null, | ||||||
|         var trailerUrl: String? = null, |         var trailerUrl: String? = null, | ||||||
|         var isAdult : Boolean? = null, |         var isAdult : Boolean? = null, | ||||||
|  |         var posterUrl: String? = null, | ||||||
|  |         var backgroundPosterUrl : String? = null, | ||||||
| 
 | 
 | ||||||
|         /** In unixtime */ |         /** In unixtime */ | ||||||
|         var startDate: Long? = null, |         var startDate: Long? = null, | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import com.lagradost.cloudstream3.mvvm.safeApiCall | ||||||
| class SyncRepo(private val repo: SyncAPI) { | class SyncRepo(private val repo: SyncAPI) { | ||||||
|     val idPrefix = repo.idPrefix |     val idPrefix = repo.idPrefix | ||||||
|     val name = repo.name |     val name = repo.name | ||||||
|  |     val icon = repo.icon | ||||||
| 
 | 
 | ||||||
|     suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> { |     suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource<Boolean> { | ||||||
|         return safeApiCall { repo.score(id, status) } |         return safeApiCall { repo.score(id, status) } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys | import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys | ||||||
| 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 | ||||||
|  | import com.lagradost.cloudstream3.ErrorLoadingException | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | 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.syncproviders.SyncAPI | ||||||
| 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 | ||||||
|  | import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject | import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject | ||||||
| import java.net.URL | import java.net.URL | ||||||
|  | @ -86,7 +88,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
| 
 | 
 | ||||||
|     override suspend fun getResult(id: String): SyncAPI.SyncResult? { |     override suspend fun getResult(id: String): SyncAPI.SyncResult? { | ||||||
|         val internalId = id.toIntOrNull() ?: return null |         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( |         return SyncAPI.SyncResult( | ||||||
|             season.id.toString(), |             season.id.toString(), | ||||||
|  | @ -100,7 +102,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|             synonyms = season.synonyms, |             synonyms = season.synonyms, | ||||||
|             isAdult = season.isAdult, |             isAdult = season.isAdult, | ||||||
|             totalEpisodes = season.episodes, |             totalEpisodes = season.episodes, | ||||||
|             //synopsis = season. |             synopsis = season.description, | ||||||
|  | 
 | ||||||
|             //TODO REST |             //TODO REST | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | @ -295,28 +298,42 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|             return fromIntToAnimeStatus(aniListStatusString.indexOf(string)) |             return fromIntToAnimeStatus(aniListStatusString.indexOf(string)) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |         private suspend fun getSeason(id: Int): SeasonResponse { | ||||||
|         private suspend fun getSeason(id: Int): SeasonResponse? { |  | ||||||
|             val q = """ |             val q = """ | ||||||
|                query (${'$'}id: Int = $id) { |                query (${'$'}id: Int = $id) { | ||||||
|                    Media (id: ${'$'}id, type: ANIME) { |                    Media (id: ${'$'}id, type: ANIME) { | ||||||
|                        id |                        id | ||||||
|                        idMal |                        idMal | ||||||
|                        coverImage |                        coverImage { | ||||||
|  |                            extraLarge | ||||||
|  |                            large | ||||||
|  |                            medium | ||||||
|  |                            color | ||||||
|  |                        } | ||||||
|                        duration |                        duration | ||||||
|                        episodes |                        episodes | ||||||
|                        genres |                        genres | ||||||
|                        synonyms |                        synonyms | ||||||
|                        averageScore |                        averageScore | ||||||
|                        isAdult |                        isAdult | ||||||
|                        trailer |                        description(asHtml: false) | ||||||
|  |                        trailer { | ||||||
|  |                            id | ||||||
|  |                            site | ||||||
|  |                            thumbnail | ||||||
|  |                        } | ||||||
|                        relations { |                        relations { | ||||||
|                             edges { |                             edges { | ||||||
|                                  id |                                  id | ||||||
|                                  relationType(version: 2) |                                  relationType(version: 2) | ||||||
|                                  node { |                                  node { | ||||||
|                                       id |                                       id | ||||||
|                                       coverImage |                                       coverImage { | ||||||
|  |                                           extraLarge | ||||||
|  |                                           large | ||||||
|  |                                           medium | ||||||
|  |                                           color | ||||||
|  |                                       } | ||||||
|                                  } |                                  } | ||||||
|                             } |                             } | ||||||
|                        } |                        } | ||||||
|  | @ -328,19 +345,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                    } |                    } | ||||||
|                } |                } | ||||||
|         """ |         """ | ||||||
| 
 |  | ||||||
|             val data = app.post( |             val data = app.post( | ||||||
|                 "https://graphql.anilist.co", |                 "https://graphql.anilist.co", | ||||||
|                 data = mapOf("query" to q), |                 data = mapOf("query" to q), | ||||||
|                 cacheTime = 0, |                 cacheTime = 0, | ||||||
|             ).text |             ).text | ||||||
|             if (data == "") return null | 
 | ||||||
|             return try { |             return tryParseJson(data) ?: throw ErrorLoadingException("Error parsing $data") | ||||||
|                 mapper.readValue(data) |  | ||||||
|             } catch (e: Exception) { |  | ||||||
|                 logError(e) |  | ||||||
|                 null |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -661,7 +672,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         val seasons = mutableListOf<SeasonResponse?>() |         val seasons = mutableListOf<SeasonResponse?>() | ||||||
|         suspend fun getSeasonRecursive(id: Int) { |         suspend fun getSeasonRecursive(id: Int) { | ||||||
|             val season = getSeason(id) |             val season = getSeason(id) | ||||||
|             if (season != null) { |  | ||||||
|             seasons.add(season) |             seasons.add(season) | ||||||
|             if (season.data?.Media?.format?.startsWith("TV") == true) { |             if (season.data?.Media?.format?.startsWith("TV") == true) { | ||||||
|                 season.data.Media.relations?.edges?.forEach { |                 season.data.Media.relations?.edges?.forEach { | ||||||
|  | @ -674,7 +684,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         } |  | ||||||
|         getSeasonRecursive(id) |         getSeasonRecursive(id) | ||||||
|         return seasons.toList() |         return seasons.toList() | ||||||
|     } |     } | ||||||
|  | @ -701,7 +710,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { | ||||||
|         @JsonProperty("averageScore") val averageScore: Int?, |         @JsonProperty("averageScore") val averageScore: Int?, | ||||||
|         @JsonProperty("isAdult") val isAdult: Boolean?, |         @JsonProperty("isAdult") val isAdult: Boolean?, | ||||||
|         @JsonProperty("trailer") val trailer: MediaTrailer?, |         @JsonProperty("trailer") val trailer: MediaTrailer?, | ||||||
| 
 |         @JsonProperty("description") val description: String?, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class MediaTrailer( |     data class MediaTrailer( | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
|  | } | ||||||
|  | @ -636,7 +636,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|         val apiName = arguments?.getString("apiName") ?: return |         val apiName = arguments?.getString("apiName") ?: return | ||||||
|         startAction = arguments?.getInt("startAction") ?: START_ACTION_NORMAL |         startAction = arguments?.getInt("startAction") ?: START_ACTION_NORMAL | ||||||
|         startValue = arguments?.getInt("startValue") ?: START_VALUE_NORMAL |         startValue = arguments?.getInt("startValue") ?: START_VALUE_NORMAL | ||||||
| 
 |  | ||||||
|         syncModel.addFromUrl(url) |         syncModel.addFromUrl(url) | ||||||
| 
 | 
 | ||||||
|         val api = getApiFromName(apiName) |         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 -> |         observe(syncModel.synced) { list -> | ||||||
|             result_sync_names?.text = |             result_sync_names?.text = | ||||||
|                 list.filter { it.isSynced && it.hasAccount }.joinToString { it.name } |                 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 -> |         observe(syncModel.metadata) { meta -> | ||||||
|             when (meta) { |             when (meta) { | ||||||
|                 is Resource.Success -> { |                 is Resource.Success -> { | ||||||
|                     val d = meta.value |                     val d = meta.value | ||||||
|                     result_sync_episodes?.max = (d.totalEpisodes ?: 0) * 1000 |                     result_sync_episodes?.max = (d.totalEpisodes ?: 0) * 1000 | ||||||
|  |                     result_sync_episodes?.progress = currentSyncProgress * 1000 | ||||||
|  | 
 | ||||||
|                     normalSafeApiCall { |                     normalSafeApiCall { | ||||||
|                         val ctx = result_sync_max_episodes?.context |                         val ctx = result_sync_max_episodes?.context | ||||||
|                         result_sync_max_episodes?.text = |                         result_sync_max_episodes?.text = | ||||||
|  | @ -1218,6 +1226,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                                 ctx?.getString(R.string.sync_total_episodes_none) |                                 ctx?.getString(R.string.sync_total_episodes_none) | ||||||
|                             } |                             } | ||||||
|                     } |                     } | ||||||
|  |                     viewModel.setMeta(d) | ||||||
|                 } |                 } | ||||||
|                 is Resource.Loading -> { |                 is Resource.Loading -> { | ||||||
|                     result_sync_max_episodes?.text = |                     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_rating?.value = d.score?.toFloat() ?: 0.0f | ||||||
|                     result_sync_check?.setItemChecked(d.status + 1, true) |                     result_sync_check?.setItemChecked(d.status + 1, true) | ||||||
|                     val watchedEpisodes = d.watchedEpisodes ?: 0 |                     val watchedEpisodes = d.watchedEpisodes ?: 0 | ||||||
|  |                     currentSyncProgress = watchedEpisodes | ||||||
|                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||||
|                         result_sync_episodes?.setProgress(watchedEpisodes * 1000, true) |                         result_sync_episodes?.setProgress(watchedEpisodes * 1000, true) | ||||||
|                     } else { |                     } else { | ||||||
|  | @ -1447,11 +1457,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|             currentId = it |             currentId = it | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         observe(viewModel.resultResponse) { data -> |         observe(viewModel.result) { data -> | ||||||
|             when (data) { |             when (data) { | ||||||
|                 is Resource.Success -> { |                 is Resource.Success -> { | ||||||
|                     val d = data.value |                     val d = data.value | ||||||
|                     if (d is LoadResponse) { |  | ||||||
|                     if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime |                     if (d !is AnimeLoadResponse && result_episode_loading.isVisible) { // no episode loading when not anime | ||||||
|                         result_episode_loading.isVisible = false |                         result_episode_loading.isVisible = false | ||||||
|                     } |                     } | ||||||
|  | @ -1533,6 +1542,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                                 setAniListSync(d.anilistId) |                                 setAniListSync(d.anilistId) | ||||||
|                             ) { |                             ) { | ||||||
|                                 syncModel.updateMetaAndUser() |                                 syncModel.updateMetaAndUser() | ||||||
|  |                             } else { | ||||||
|  |                                 syncModel.addFromUrl(d.url) | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|  | @ -1799,7 +1810,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                         result_meta_type?.text = it |                         result_meta_type?.text = it | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|                     when (d) { |                     when (d) { | ||||||
|                         is AnimeLoadResponse -> { |                         is AnimeLoadResponse -> { | ||||||
| 
 | 
 | ||||||
|  | @ -1811,9 +1821,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                         } |                         } | ||||||
|                         else -> result_title.text = d.name |                         else -> result_title.text = d.name | ||||||
|                     } |                     } | ||||||
|                     } else { |  | ||||||
|                         updateVisStatus(1) |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|                 is Resource.Failure -> { |                 is Resource.Failure -> { | ||||||
|                     result_error_text.text = url?.plus("\n") + data.errorString |                     result_error_text.text = url?.plus("\n") + data.errorString | ||||||
|  | @ -1873,7 +1880,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                     it.context?.openBrowser(tempUrl) |                     it.context?.openBrowser(tempUrl) | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (restart || viewModel.resultResponse.value == null) { |                 if (restart || viewModel.result.value == null) { | ||||||
|                     //viewModel.clear() |                     //viewModel.clear() | ||||||
|                     viewModel.load(tempUrl, apiName, showFillers) |                     viewModel.load(tempUrl, apiName, showFillers) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.ui.result | package com.lagradost.cloudstream3.ui.result | ||||||
| 
 | 
 | ||||||
| import android.content.Context | import android.util.Log | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||||
| import androidx.lifecycle.ViewModel | 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.AcraApplication.Companion.setKey | ||||||
| import com.lagradost.cloudstream3.mvvm.Resource | import com.lagradost.cloudstream3.mvvm.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.safeApiCall | import com.lagradost.cloudstream3.mvvm.safeApiCall | ||||||
|  | import com.lagradost.cloudstream3.syncproviders.SyncAPI | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository | import com.lagradost.cloudstream3.ui.APIRepository | ||||||
| import com.lagradost.cloudstream3.ui.WatchType | import com.lagradost.cloudstream3.ui.WatchType | ||||||
| import com.lagradost.cloudstream3.ui.player.IGenerator | import com.lagradost.cloudstream3.ui.player.IGenerator | ||||||
|  | @ -43,7 +44,7 @@ class ResultViewModel : ViewModel() { | ||||||
|     private var repo: APIRepository? = null |     private var repo: APIRepository? = null | ||||||
|     private var generator: IGenerator? = 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 _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData() | ||||||
|     private val episodeById: MutableLiveData<HashMap<Int, Int>> = |     private val episodeById: MutableLiveData<HashMap<Int, Int>> = | ||||||
|         MutableLiveData() // lookup by ID to get Index |         MutableLiveData() // lookup by ID to get Index | ||||||
|  | @ -55,7 +56,8 @@ class ResultViewModel : ViewModel() { | ||||||
|     private val selectedRangeInt: MutableLiveData<Int> = MutableLiveData() |     private val selectedRangeInt: MutableLiveData<Int> = MutableLiveData() | ||||||
|     val rangeOptions: LiveData<List<String>> = _rangeOptions |     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 episodes: LiveData<List<ResultEpisode>> get() = _episodes | ||||||
|     val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes |     val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes | ||||||
|     val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount |     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) { |     private fun loadWatchStatus(localId: Int? = null) { | ||||||
|         val currentId = localId ?: id.value ?: return |         val currentId = localId ?: id.value ?: return | ||||||
|         val currentWatch = getResultWatchState(currentId) |         val currentWatch = getResultWatchState(currentId) | ||||||
|  | @ -289,7 +326,7 @@ class ResultViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|         when (data) { |         when (data) { | ||||||
|             is Resource.Success -> { |             is Resource.Success -> { | ||||||
|                 val d = data.value |                 val d = applyMeta(data.value, lastMeta) | ||||||
|                 page.postValue(d) |                 page.postValue(d) | ||||||
|                 val mainId = d.getId() |                 val mainId = d.getId() | ||||||
|                 id.postValue(mainId) |                 id.postValue(mainId) | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ data class CurrentSynced( | ||||||
|     val idPrefix: String, |     val idPrefix: String, | ||||||
|     val isSynced: Boolean, |     val isSynced: Boolean, | ||||||
|     val hasAccount: Boolean, |     val hasAccount: Boolean, | ||||||
|  |     val icon : Int, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| class SyncViewModel : ViewModel() { | class SyncViewModel : ViewModel() { | ||||||
|  | @ -48,7 +49,8 @@ class SyncViewModel : ViewModel() { | ||||||
|                 it.name, |                 it.name, | ||||||
|                 it.idPrefix, |                 it.idPrefix, | ||||||
|                 syncIds.containsKey(it.idPrefix), |                 syncIds.containsKey(it.idPrefix), | ||||||
|                 it.hasAccount() |                 it.hasAccount(), | ||||||
|  |                 it.icon, | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -67,8 +69,13 @@ class SyncViewModel : ViewModel() { | ||||||
|         updateSynced() |         updateSynced() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     var hasAddedFromUrl : HashSet<String> = hashSetOf() | ||||||
|  | 
 | ||||||
|     fun addFromUrl(url: String?) = viewModelScope.launch { |     fun addFromUrl(url: String?) = viewModelScope.launch { | ||||||
|  |         if(url == null || hasAddedFromUrl.contains(url)) return@launch | ||||||
|         SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) -> |         SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) -> | ||||||
|  |             hasAddedFromUrl.add(url) | ||||||
|  | 
 | ||||||
|             setMalId(malId) |             setMalId(malId) | ||||||
|             setAniListId(aniListId) |             setAniListId(aniListId) | ||||||
|             if (malId != null || aniListId != null) { |             if (malId != null || aniListId != null) { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.utils | package com.lagradost.cloudstream3.utils | ||||||
| 
 | 
 | ||||||
|  | import android.util.Log | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
|  | @ -14,6 +15,8 @@ object SyncUtil { | ||||||
|         Regex("""(twist\.moe)/a/([^/?]*)"""), |         Regex("""(twist\.moe)/a/([^/?]*)"""), | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |     private const val TAG = "SNC" | ||||||
|  | 
 | ||||||
|     private const val GOGOANIME = "Gogoanime" |     private const val GOGOANIME = "Gogoanime" | ||||||
|     private const val NINE_ANIME = "9anime" |     private const val NINE_ANIME = "9anime" | ||||||
|     private const val TWIST_MOE = "Twistmoe" |     private const val TWIST_MOE = "Twistmoe" | ||||||
|  | @ -28,6 +31,7 @@ object SyncUtil { | ||||||
| 
 | 
 | ||||||
|     suspend fun getIdsFromUrl(url: String?): Pair<String?, String?>? { |     suspend fun getIdsFromUrl(url: String?): Pair<String?, String?>? { | ||||||
|         if (url == null) return null |         if (url == null) return null | ||||||
|  |         Log.i(TAG, "getIdsFromUrl $url") | ||||||
| 
 | 
 | ||||||
|         for (regex in regexs) { |         for (regex in regexs) { | ||||||
|             regex.find(url)?.let { match -> |             regex.find(url)?.let { match -> | ||||||
|  | @ -51,6 +55,7 @@ object SyncUtil { | ||||||
|         slug: String, |         slug: String, | ||||||
|         site: String = "GogoanimeGogoanime" |         site: String = "GogoanimeGogoanime" | ||||||
|     ): Pair<String?, String?>? { |     ): Pair<String?, String?>? { | ||||||
|  |         Log.i(TAG, "getIdsFromSlug $slug $site") | ||||||
|         try { |         try { | ||||||
|             //Gogoanime, Twistmoe and 9anime |             //Gogoanime, Twistmoe and 9anime | ||||||
|             val url = |             val url = | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|         android:width="24dp" |         android:width="20dp" | ||||||
|         android:height="24dp" |         android:height="20dp" | ||||||
|         android:viewportWidth="172" |         android:viewportWidth="172" | ||||||
|         android:viewportHeight="172" |         android:viewportHeight="172" | ||||||
|         android:tint="?attr/white" |         android:tint="?attr/white" | ||||||
|  |  | ||||||
|  | @ -375,6 +375,7 @@ | ||||||
|                         </LinearLayout> |                         </LinearLayout> | ||||||
|                     </LinearLayout> |                     </LinearLayout> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|                     <com.google.android.material.button.MaterialButton |                     <com.google.android.material.button.MaterialButton | ||||||
|                             android:id="@+id/result_bookmark_button" |                             android:id="@+id/result_bookmark_button" | ||||||
|                             style="@style/BlackButton" |                             style="@style/BlackButton" | ||||||
|  |  | ||||||
|  | @ -12,7 +12,12 @@ | ||||||
|             android:paddingEnd="@dimen/result_padding" |             android:paddingEnd="@dimen/result_padding" | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="wrap_content"> |             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 |             <ImageView | ||||||
|                     android:nextFocusDown="@id/result_bookmark_button" |                     android:nextFocusDown="@id/result_bookmark_button" | ||||||
|                     android:nextFocusRight="@id/result_share" |                     android:nextFocusRight="@id/result_share" | ||||||
|  | @ -28,6 +33,24 @@ | ||||||
|                     android:src="@drawable/ic_baseline_arrow_back_24" |                     android:src="@drawable/ic_baseline_arrow_back_24" | ||||||
|                     android:contentDescription="@string/go_back" |                     android:contentDescription="@string/go_back" | ||||||
|                     app:tint="?attr/white" /> |                     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 |         <LinearLayout | ||||||
|                 android:gravity="end" |                 android:gravity="end" | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								app/src/main/res/layout/result_mini_image.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/layout/result_mini_image.xml
									
										
									
									
									
										Normal 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> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue