Warn possible duplicates for favorites and subscriptions

This commit is contained in:
Luna712 2023-10-14 13:21:08 -06:00
parent 3cb2196e62
commit 194f1b46fb
6 changed files with 203 additions and 85 deletions

View file

@ -74,17 +74,17 @@ class HomeViewModel : ViewModel() {
val watchPos = getViewPos(resume.episodeId) val watchPos = getViewPos(resume.episodeId)
DataStoreHelper.ResumeWatchingResult( DataStoreHelper.ResumeWatchingResult(
watchPos,
resume.parentId,
resume.episode,
resume.season,
resume.isFromDownload,
resume.episodeId,
data.name, data.name,
data.url, data.url,
data.apiName, data.apiName,
data.type, data.type,
data.poster, data.poster
watchPos,
resume.episodeId,
resume.parentId,
resume.episode,
resume.season,
resume.isFromDownload
) )
} }
} }

View file

@ -431,7 +431,11 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}) })
resultSubscribe.setOnClickListener { resultSubscribe.setOnClickListener {
val isSubscribed = val isSubscribed =
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener viewModel.toggleSubscriptionStatus(context) ?: return@setOnClickListener
if (viewModel.subscribeStatus.value == isSubscribed) {
return@setOnClickListener
}
val message = if (isSubscribed) { val message = if (isSubscribed) {
// Kinda icky to have this here, but it works. // Kinda icky to have this here, but it works.
@ -447,7 +451,11 @@ open class ResultFragmentPhone : FullScreenPlayer() {
} }
resultFavorite.setOnClickListener { resultFavorite.setOnClickListener {
val isFavorite = val isFavorite =
viewModel.toggleFavoriteStatus() ?: return@setOnClickListener viewModel.toggleFavoriteStatus(context) ?: return@setOnClickListener
if (viewModel.favoriteStatus.value == isFavorite) {
return@setOnClickListener
}
val message = if (isFavorite) { val message = if (isFavorite) {
R.string.favorite_added R.string.favorite_added

View file

@ -557,7 +557,11 @@ class ResultFragmentTv : Fragment() {
setIconResource(drawable) setIconResource(drawable)
setText(text) setText(text)
setOnClickListener { setOnClickListener {
val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener val isFavorite = viewModel.toggleFavoriteStatus(context) ?: return@setOnClickListener
if (viewModel.favoriteStatus.value == isFavorite) {
return@setOnClickListener
}
val message = if (isFavorite) { val message = if (isFavorite) {
R.string.favorite_added R.string.favorite_added

View file

@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -28,6 +29,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
@ -45,18 +47,23 @@ import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.CastHelper.startCast import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getSubscribedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeSubscribedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
@ -66,6 +73,9 @@ import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
interface AlertDialogResponseCallback {
fun onUserResponse(response: Boolean)
}
/** This starts at 1 */ /** This starts at 1 */
data class EpisodeRange( data class EpisodeRange(
@ -452,15 +462,15 @@ class ResultViewModel2 : ViewModel() {
DataStoreHelper.setBookmarkedData( DataStoreHelper.setBookmarkedData(
currentId, currentId,
DataStoreHelper.BookmarkedData( DataStoreHelper.BookmarkedData(
currentId,
current?.bookmarkedTime ?: currentTime, current?.bookmarkedTime ?: currentTime,
currentTime, currentTime,
currentResponse.year,
currentId,
currentResponse.name, currentResponse.name,
currentResponse.url, currentResponse.url,
currentResponse.apiName, currentResponse.apiName,
currentResponse.type, currentResponse.type,
currentResponse.posterUrl, currentResponse.posterUrl
currentResponse.year
) )
) )
if (currentWatchType != status) { if (currentWatchType != status) {
@ -841,7 +851,7 @@ class ResultViewModel2 : ViewModel() {
/** /**
* @return true if the new status is Subscribed, false if not. Null if not possible to subscribe. * @return true if the new status is Subscribed, false if not. Null if not possible to subscribe.
**/ **/
fun toggleSubscriptionStatus(): Boolean? { fun toggleSubscriptionStatus(context: Context?): Boolean? {
val isSubscribed = _subscribeStatus.value ?: return null val isSubscribed = _subscribeStatus.value ?: return null
val response = currentResponse ?: return null val response = currentResponse ?: return null
if (response !is EpisodeResponse) return null if (response !is EpisodeResponse) return null
@ -849,35 +859,61 @@ class ResultViewModel2 : ViewModel() {
val currentId = response.getId() val currentId = response.getId()
if (isSubscribed) { if (isSubscribed) {
DataStoreHelper.removeSubscribedData(currentId) removeSubscribedData(currentId)
_subscribeStatus.postValue(!isSubscribed)
return !isSubscribed
} else { } else {
val current = DataStoreHelper.getSubscribedData(currentId) checkAndWarnDuplicates(
context,
R.string.subscription_duplicate_title,
R.string.subscription_duplicate_message,
response.name,
getAllSubscriptions(),
object : AlertDialogResponseCallback {
override fun onUserResponse(action: Boolean) {
if (!action) {
_subscribeStatus.postValue(false)
return
}
val current = getSubscribedData(currentId)
DataStoreHelper.setSubscribedData( DataStoreHelper.setSubscribedData(
currentId, currentId,
DataStoreHelper.SubscribedData( DataStoreHelper.SubscribedData(
currentId, current?.subscribedTime ?: unixTimeMS,
current?.bookmarkedTime ?: unixTimeMS,
unixTimeMS, unixTimeMS,
response.getLatestEpisodes(), response.getLatestEpisodes(),
response.year,
currentId,
response.name, response.name,
response.url, response.url,
response.apiName, response.apiName,
response.type, response.type,
response.posterUrl, response.posterUrl
response.year
) )
) )
}
_subscribeStatus.postValue(!isSubscribed) _subscribeStatus.postValue(true)
return !isSubscribed
SubscriptionWorkManager.enqueuePeriodicWork(context)
val name = (page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
showToast(txt(R.string.subscription_new, name), Toast.LENGTH_SHORT)
}
}
)
return _subscribeStatus.value
}
} }
/** /**
* @return true if added to favorites, false if not. Null if not possible to favorite. * @return true if added to favorites, false if not. Null if not possible to favorite.
**/ **/
fun toggleFavoriteStatus(): Boolean? { fun toggleFavoriteStatus(context: Context?): Boolean? {
val isFavorite = _favoriteStatus.value ?: return null val isFavorite = _favoriteStatus.value ?: return null
val response = currentResponse ?: return null val response = currentResponse ?: return null
@ -885,27 +921,81 @@ class ResultViewModel2 : ViewModel() {
if (isFavorite) { if (isFavorite) {
removeFavoritesData(currentId) removeFavoritesData(currentId)
_favoriteStatus.postValue(!isFavorite)
return !isFavorite
} else { } else {
checkAndWarnDuplicates(
context,
R.string.favorites_duplicate_title,
R.string.favorites_duplicate_message,
response.name,
getAllFavorites(),
object : AlertDialogResponseCallback {
override fun onUserResponse(action: Boolean) {
if (!action) {
_favoriteStatus.postValue(false)
return
}
val current = getFavoritesData(currentId) val current = getFavoritesData(currentId)
setFavoritesData( setFavoritesData(
currentId, currentId,
DataStoreHelper.FavoritesData( DataStoreHelper.FavoritesData(
currentId,
current?.favoritesTime ?: unixTimeMS, current?.favoritesTime ?: unixTimeMS,
unixTimeMS, unixTimeMS,
response.year,
currentId,
response.name, response.name,
response.url, response.url,
response.apiName, response.apiName,
response.type, response.type,
response.posterUrl, response.posterUrl
response.year
) )
) )
_favoriteStatus.postValue(true)
val name = (page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
showToast(txt(R.string.favorite_added, name), Toast.LENGTH_SHORT)
}
}
)
return _favoriteStatus.value
}
} }
_favoriteStatus.postValue(!isFavorite) private fun checkAndWarnDuplicates(
return !isFavorite context: Context?,
title: Int,
message: Int,
name: String,
data: List<DataStoreHelper.BaseSearchResponse>,
callback: AlertDialogResponseCallback)
{
val isDuplicate = data.any { it.name == name }
if (!isDuplicate || context == null) {
callback.onUserResponse(true)
return
}
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setTitle(title)
builder.setMessage(message)
builder.setNegativeButton(R.string.cancel) { _, _ ->
callback.onUserResponse(false)
}
builder.setPositiveButton(R.string.ignore) { _, _ ->
callback.onUserResponse(true)
}
builder.show().setDefaultFocus()
} }
private fun startChromecast( private fun startChromecast(

View file

@ -352,20 +352,31 @@ object DataStoreHelper {
/** /**
* Used to display notifications on new episodes and posters in library. * Used to display notifications on new episodes and posters in library.
**/ **/
data class SubscribedData( abstract class BaseSearchResponse(
@JsonProperty("id") override var id: Int?, @JsonProperty("id") override var id: Int?,
@JsonProperty("subscribedTime") val bookmarkedTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
@JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map<DubStatus, Int?>,
@JsonProperty("name") override val name: String, @JsonProperty("name") override val name: String,
@JsonProperty("url") override val url: String, @JsonProperty("url") override val url: String,
@JsonProperty("apiName") override val apiName: String, @JsonProperty("apiName") override val apiName: String,
@JsonProperty("type") override var type: TvType? = null, @JsonProperty("type") override var type: TvType? = null,
@JsonProperty("posterUrl") override var posterUrl: String?, @JsonProperty("posterUrl") override var posterUrl: String?,
@JsonProperty("year") val year: Int?,
@JsonProperty("quality") override var quality: SearchQuality? = null, @JsonProperty("quality") override var quality: SearchQuality? = null,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null, @JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null
) : SearchResponse { ) : SearchResponse
data class SubscribedData(
@JsonProperty("subscribedTime") val subscribedTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
@JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map<DubStatus, Int?>,
@JsonProperty("year") val year: Int?,
override var id: Int?,
override val name: String,
override val url: String,
override val apiName: String,
override var type: TvType?,
override var posterUrl: String?,
override var quality: SearchQuality? = null,
override var posterHeaders: Map<String, String>? = null
) : BaseSearchResponse(id, name, url, apiName, type, posterUrl, quality, posterHeaders) {
fun toLibraryItem(): SyncAPI.LibraryItem? { fun toLibraryItem(): SyncAPI.LibraryItem? {
return SyncAPI.LibraryItem( return SyncAPI.LibraryItem(
name, name,
@ -381,18 +392,18 @@ object DataStoreHelper {
} }
data class BookmarkedData( data class BookmarkedData(
@JsonProperty("id") override var id: Int?,
@JsonProperty("bookmarkedTime") val bookmarkedTime: Long, @JsonProperty("bookmarkedTime") val bookmarkedTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: 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("year") val year: Int?,
@JsonProperty("quality") override var quality: SearchQuality? = null, override var id: Int?,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null, override val name: String,
) : SearchResponse { override val url: String,
override val apiName: String,
override var type: TvType?,
override var posterUrl: String?,
override var quality: SearchQuality? = null,
override var posterHeaders: Map<String, String>? = null
) : BaseSearchResponse(id, name, url, apiName, type, posterUrl, quality, posterHeaders) {
fun toLibraryItem(id: String): SyncAPI.LibraryItem { fun toLibraryItem(id: String): SyncAPI.LibraryItem {
return SyncAPI.LibraryItem( return SyncAPI.LibraryItem(
name, name,
@ -408,18 +419,18 @@ object DataStoreHelper {
} }
data class FavoritesData( data class FavoritesData(
@JsonProperty("id") override var id: Int?,
@JsonProperty("favoritesTime") val favoritesTime: Long, @JsonProperty("favoritesTime") val favoritesTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: 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("year") val year: Int?,
@JsonProperty("quality") override var quality: SearchQuality? = null, override var id: Int?,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null, override val name: String,
) : SearchResponse { override val url: String,
override val apiName: String,
override var type: TvType?,
override var posterUrl: String?,
override var quality: SearchQuality? = null,
override var posterHeaders: Map<String, String>? = null
) : BaseSearchResponse(id, name, url, apiName, type, posterUrl, quality, posterHeaders) {
fun toLibraryItem(): SyncAPI.LibraryItem? { fun toLibraryItem(): SyncAPI.LibraryItem? {
return SyncAPI.LibraryItem( return SyncAPI.LibraryItem(
name, name,
@ -435,20 +446,20 @@ object DataStoreHelper {
} }
data class ResumeWatchingResult( data class ResumeWatchingResult(
@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("watchPos") val watchPos: PosDur?, @JsonProperty("watchPos") val watchPos: PosDur?,
@JsonProperty("id") override var id: Int?,
@JsonProperty("parentId") val parentId: Int?, @JsonProperty("parentId") val parentId: Int?,
@JsonProperty("episode") val episode: Int?, @JsonProperty("episode") val episode: Int?,
@JsonProperty("season") val season: Int?, @JsonProperty("season") val season: Int?,
@JsonProperty("isFromDownload") val isFromDownload: Boolean, @JsonProperty("isFromDownload") val isFromDownload: Boolean,
@JsonProperty("quality") override var quality: SearchQuality? = null, override var id: Int?,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null, override val name: String,
) : SearchResponse override val url: String,
override val apiName: String,
override var type: TvType?,
override var posterUrl: String?,
override var quality: SearchQuality? = null,
override var posterHeaders: Map<String, String>? = null
) : BaseSearchResponse(id, name, url, apiName, type, posterUrl, quality, posterHeaders)
/** /**
* A datastore wide account for future implementations of a multiple account system * A datastore wide account for future implementations of a multiple account system

View file

@ -695,4 +695,9 @@
<string name="favorite_removed">%s removed from favorites</string> <string name="favorite_removed">%s removed from favorites</string>
<string name="action_add_to_favorites">Add to favorites</string> <string name="action_add_to_favorites">Add to favorites</string>
<string name="action_remove_from_favorites">Remove from favorites</string> <string name="action_remove_from_favorites">Remove from favorites</string>
<string name="subscription_duplicate_title">Possible Duplicate Subscription</string>
<string name="subscription_duplicate_message">A possible duplicate with the same name already exists in your subscrptions.</string>
<string name="favorites_duplicate_title">Possible Duplicate Found</string>
<string name="favorites_duplicate_message">A possible duplicate with the same name already exists in your favorites.</string>
<string name="ignore">Ignore</string>
</resources> </resources>