From afd70b9c1833beb9f0ed14e200b343c1d1ab5842 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:21:35 -0600 Subject: [PATCH] Add favorites --- .../syncproviders/providers/LocalList.kt | 5 +- .../ui/result/ResultFragmentPhone.kt | 27 ++++++++++ .../ui/result/ResultViewModel2.kt | 47 +++++++++++++++++ .../cloudstream3/utils/DataStoreHelper.kt | 51 +++++++++++++++++++ .../main/res/layout/fragment_result_swipe.xml | 21 +++++++- app/src/main/res/values/strings.xml | 3 ++ 6 files changed, 151 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index e6ca9711..e3f88adc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -9,6 +9,7 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioWork +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData @@ -77,13 +78,15 @@ class LocalList : SyncAPI { } } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { it.toLibraryItem() + }) + mapOf(R.string.favorites_list_name to getAllFavorites().mapNotNull { + it.toLibraryItem() }) } val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { // None is not something to display it.stringRes to emptyList() - } + mapOf(R.string.subscription_list_name to emptyList()) + } + mapOf(R.string.subscription_list_name to emptyList()) + mapOf(R.string.favorites_list_name to emptyList()) return SyncAPI.LibraryMetadata( (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e5f16dd5..a0d82062 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -445,6 +445,20 @@ open class ResultFragmentPhone : FullScreenPlayer() { ?: txt(R.string.no_data).asStringNull(context) ?: "" CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) } + resultFavorite.setOnClickListener { + val isFavorite = + viewModel.toggleFavoriteStatus() ?: return@setOnClickListener + + val message = if (isFavorite) { + R.string.favorite_added + } else { + R.string.favorite_removed + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } mediaRouteButton.apply { val chromecastSupport = api?.hasChromecastSupport == true alpha = if (chromecastSupport) 1f else 0.3f @@ -564,6 +578,19 @@ open class ResultFragmentPhone : FullScreenPlayer() { binding?.resultSubscribe?.setImageResource(drawable) } + observeNullable(viewModel.favoriteStatus) { isFavorite -> + binding?.resultFavorite?.isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable + + val drawable = if (isFavorite) { + R.drawable.ic_baseline_favorite_24 + } else { + R.drawable.ic_baseline_favorite_border_24 + } + + binding?.resultFavorite?.setImageResource(drawable) + } + observe(viewModel.trailers) { trailers -> setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet! } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 6acf476a..e5ed7b92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -51,11 +51,14 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.UIHelper.navigate @@ -425,6 +428,9 @@ class ResultViewModel2 : ViewModel() { private val _subscribeStatus: MutableLiveData = MutableLiveData(null) val subscribeStatus: LiveData = _subscribeStatus + private val _favoriteStatus: MutableLiveData = MutableLiveData(null) + val favoriteStatus: LiveData = _favoriteStatus + companion object { const val TAG = "RVM2" //private const val EPISODE_RANGE_SIZE = 20 @@ -868,6 +874,40 @@ class ResultViewModel2 : ViewModel() { return !isSubscribed } + /** + * @return true if added to favorites, false if not. Null if not possible to favorite. + **/ + fun toggleFavoriteStatus(): Boolean? { + val isFavorite = _favoriteStatus.value ?: return null + val response = currentResponse ?: return null + + val currentId = response.getId() + + if (isFavorite) { + removeFavoritesData(currentId) + } else { + val current = getFavoritesData(currentId) + + setFavoritesData( + currentId, + DataStoreHelper.FavoritesData( + currentId, + current?.favoritesTime ?: unixTimeMS, + unixTimeMS, + response.name, + response.url, + response.apiName, + response.type, + response.posterUrl, + response.year + ) + ) + } + + _favoriteStatus.postValue(!isFavorite) + return !isFavorite + } + private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1750,6 +1790,12 @@ class ResultViewModel2 : ViewModel() { } } + private fun postFavorites(loadResponse: LoadResponse) { + val id = loadResponse.getId() + val isFavorite = getFavoritesData(id) != null + _favoriteStatus.postValue(isFavorite) + } + private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return @@ -1887,6 +1933,7 @@ class ResultViewModel2 : ViewModel() { currentResponse = loadResponse postPage(loadResponse, apiRepository) postSubscription(loadResponse) + postFavorites(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 952422a4..4b4157d6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -42,6 +42,7 @@ const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" +const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -406,6 +407,33 @@ object DataStoreHelper { } } + data class FavoritesData( + @JsonProperty("id") override var id: Int?, + @JsonProperty("favoritesTime") val favoritesTime: Long, + @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override var type: TvType? = null, + @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("year") val year: Int?, + @JsonProperty("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, + ) : SearchResponse { + fun toLibraryItem(): SyncAPI.LibraryItem? { + return SyncAPI.LibraryItem( + name, + url, + id?.toString() ?: return null, + null, + null, + null, + latestUpdatedTime, + apiName, type, posterUrl, posterHeaders, quality, this.id + ) + } + } + data class ResumeWatchingResult( @JsonProperty("name") override val name: String, @JsonProperty("url") override val url: String, @@ -579,6 +607,29 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) } + fun getAllFavorites(): List { + return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull { + getKey(it) + } ?: emptyList() + } + + fun removeFavoritesData(id: Int?) { + if (id == null) return + AccountManager.localListApi.requireLibraryRefresh = true + removeKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + + fun setFavoritesData(id: Int?, data: FavoritesData) { + if (id == null) return + setKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString(), data) + AccountManager.localListApi.requireLibraryRefresh = true + } + + fun getFavoritesData(id: Int?): FavoritesData? { + if (id == null) return null + return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short diff --git a/app/src/main/res/layout/fragment_result_swipe.xml b/app/src/main/res/layout/fragment_result_swipe.xml index 4e8e3c14..eb2653d0 100644 --- a/app/src/main/res/layout/fragment_result_swipe.xml +++ b/app/src/main/res/layout/fragment_result_swipe.xml @@ -74,7 +74,7 @@ android:nextFocusUp="@id/result_back" android:nextFocusDown="@id/result_description" android:nextFocusLeft="@id/result_add_sync" - android:nextFocusRight="@id/result_share" + android:nextFocusRight="@id/result_favorite" tools:visibility="visible" @@ -89,10 +89,27 @@ android:layout_gravity="end|center_vertical" app:tint="?attr/textColor" /> + + tv_no_focus_tag You have already voted + Favorites + %s added to favorites + %s removed from favorites