mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Warn of potential duplicates when adding to library (#691)
This commit is contained in:
parent
e4ba852007
commit
968bd59188
9 changed files with 481 additions and 166 deletions
|
@ -1246,6 +1246,18 @@ interface LoadResponse {
|
||||||
return this.syncData[aniListIdPrefix]
|
return this.syncData[aniListIdPrefix]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.getImdbId(): String? {
|
||||||
|
return normalSafeApiCall {
|
||||||
|
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LoadResponse.getTMDbId(): String? {
|
||||||
|
return normalSafeApiCall {
|
||||||
|
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun LoadResponse.addMalId(id: Int?) {
|
fun LoadResponse.addMalId(id: Int?) {
|
||||||
this.syncData[malIdPrefix] = (id ?: return).toString()
|
this.syncData[malIdPrefix] = (id ?: return).toString()
|
||||||
this.addSimklId(SimklApi.Companion.SyncServices.Mal, id.toString())
|
this.addSimklId(SimklApi.Companion.SyncServices.Mal, id.toString())
|
||||||
|
|
|
@ -1306,7 +1306,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
this@MainActivity.getString(R.string.action_add_to_bookmarks),
|
this@MainActivity.getString(R.string.action_add_to_bookmarks),
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
viewModel.updateWatchStatus(WatchType.values()[it])
|
viewModel.updateWatchStatus(WatchType.values()[it], this@MainActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read the id string to get all other ids */
|
/** Read the id string to get all other ids */
|
||||||
private fun readIdFromString(idString: String?): Map<SyncServices, String> {
|
fun readIdFromString(idString: String?): Map<SyncServices, String> {
|
||||||
return tryParseJson(idString) ?: return emptyMap()
|
return tryParseJson(idString) ?: return emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -378,6 +378,14 @@ class HomeParentItemAdapterPreview(
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
val newValue = WatchType.values()[it]
|
val newValue = WatchType.values()[it]
|
||||||
|
|
||||||
|
ResultViewModel2().updateWatchStatus(
|
||||||
|
newValue,
|
||||||
|
fab.context,
|
||||||
|
item
|
||||||
|
) { statusChanged: Boolean ->
|
||||||
|
if (!statusChanged) return@updateWatchStatus
|
||||||
|
|
||||||
homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
|
homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
null,
|
null,
|
||||||
ContextCompat.getDrawable(
|
ContextCompat.getDrawable(
|
||||||
|
@ -388,11 +396,7 @@ class HomeParentItemAdapterPreview(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
homePreviewBookmark.setText(newValue.stringRes)
|
homePreviewBookmark.setText(newValue.stringRes)
|
||||||
|
}
|
||||||
ResultViewModel2.updateWatchStatus(
|
|
||||||
item,
|
|
||||||
newValue
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,10 +430,10 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
resultSubscribe.setOnClickListener {
|
resultSubscribe.setOnClickListener {
|
||||||
val isSubscribed =
|
viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
|
||||||
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
|
if (newStatus == null) return@toggleSubscriptionStatus
|
||||||
|
|
||||||
val message = if (isSubscribed) {
|
val message = if (newStatus) {
|
||||||
// Kinda icky to have this here, but it works.
|
// Kinda icky to have this here, but it works.
|
||||||
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
||||||
R.string.subscription_new
|
R.string.subscription_new
|
||||||
|
@ -445,11 +445,12 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||||
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
|
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resultFavorite.setOnClickListener {
|
resultFavorite.setOnClickListener {
|
||||||
val isFavorite =
|
viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? ->
|
||||||
viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
|
if (newStatus == null) return@toggleFavoriteStatus
|
||||||
|
|
||||||
val message = if (isFavorite) {
|
val message = if (newStatus) {
|
||||||
R.string.favorite_added
|
R.string.favorite_added
|
||||||
} else {
|
} else {
|
||||||
R.string.favorite_removed
|
R.string.favorite_removed
|
||||||
|
@ -459,6 +460,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||||
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
|
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mediaRouteButton.apply {
|
mediaRouteButton.apply {
|
||||||
val chromecastSupport = api?.hasChromecastSupport == true
|
val chromecastSupport = api?.hasChromecastSupport == true
|
||||||
alpha = if (chromecastSupport) 1f else 0.3f
|
alpha = if (chromecastSupport) 1f else 0.3f
|
||||||
|
@ -960,7 +962,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
fab.context.getString(R.string.action_add_to_bookmarks),
|
fab.context.getString(R.string.action_add_to_bookmarks),
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
viewModel.updateWatchStatus(WatchType.values()[it])
|
viewModel.updateWatchStatus(WatchType.values()[it], context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -531,7 +531,7 @@ class ResultFragmentTv : Fragment() {
|
||||||
view.context.getString(R.string.action_add_to_bookmarks),
|
view.context.getString(R.string.action_add_to_bookmarks),
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
viewModel.updateWatchStatus(WatchType.values()[it])
|
viewModel.updateWatchStatus(WatchType.values()[it], context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,9 +557,10 @@ class ResultFragmentTv : Fragment() {
|
||||||
setIconResource(drawable)
|
setIconResource(drawable)
|
||||||
setText(text)
|
setText(text)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
|
viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? ->
|
||||||
|
if (newStatus == null) return@toggleFavoriteStatus
|
||||||
|
|
||||||
val message = if (isFavorite) {
|
val message = if (newStatus) {
|
||||||
R.string.favorite_added
|
R.string.favorite_added
|
||||||
} else {
|
} else {
|
||||||
R.string.favorite_removed
|
R.string.favorite_removed
|
||||||
|
@ -571,6 +572,7 @@ class ResultFragmentTv : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
observeNullable(viewModel.movie) { data ->
|
observeNullable(viewModel.movie) { data ->
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
|
|
|
@ -7,6 +7,8 @@ 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.annotation.MainThread
|
||||||
|
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
|
||||||
|
@ -31,6 +33,7 @@ import com.lagradost.cloudstream3.mvvm.*
|
||||||
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
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
|
||||||
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.download.DOWNLOAD_NAVIGATE_TO
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||||
|
@ -45,22 +48,37 @@ 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.deleteBookmarkedData
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllBookmarkedData
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||||
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.getLastWatched
|
||||||
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.getVideoWatchState
|
||||||
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.setBookmarkedData
|
||||||
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
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setSubscribedData
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -113,6 +131,18 @@ data class ResultData(
|
||||||
val plotHeaderText: UiText,
|
val plotHeaderText: UiText,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class CheckDuplicateData(
|
||||||
|
val name: String,
|
||||||
|
val year: Int?,
|
||||||
|
val syncData: Map<String, String>?
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class LibraryListType {
|
||||||
|
BOOKMARKS,
|
||||||
|
FAVORITES,
|
||||||
|
SUBSCRIPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
fun txt(status: DubStatus?): UiText? {
|
fun txt(status: DubStatus?): UiText? {
|
||||||
return txt(
|
return txt(
|
||||||
when (status) {
|
when (status) {
|
||||||
|
@ -441,33 +471,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
return this?.firstOrNull { it.season == season }
|
return this?.firstOrNull { it.season == season }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
|
|
||||||
val currentId = currentResponse.getId()
|
|
||||||
|
|
||||||
val currentWatchType = getResultWatchState(currentId)
|
|
||||||
|
|
||||||
DataStoreHelper.setResultWatchState(currentId, status.internalId)
|
|
||||||
val current = DataStoreHelper.getBookmarkedData(currentId)
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
DataStoreHelper.setBookmarkedData(
|
|
||||||
currentId,
|
|
||||||
DataStoreHelper.BookmarkedData(
|
|
||||||
currentId,
|
|
||||||
current?.bookmarkedTime ?: currentTime,
|
|
||||||
currentTime,
|
|
||||||
currentResponse.name,
|
|
||||||
currentResponse.url,
|
|
||||||
currentResponse.apiName,
|
|
||||||
currentResponse.type,
|
|
||||||
currentResponse.posterUrl,
|
|
||||||
currentResponse.year
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (currentWatchType != status) {
|
|
||||||
MainActivity.bookmarksUpdatedEvent(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterName(name: String?): String? {
|
private fun filterName(name: String?): String? {
|
||||||
if (name == null) return null
|
if (name == null) return null
|
||||||
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
|
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
|
||||||
|
@ -822,9 +825,77 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val selectPopup: LiveData<SelectPopup?> = _selectPopup
|
val selectPopup: LiveData<SelectPopup?> = _selectPopup
|
||||||
|
|
||||||
|
|
||||||
fun updateWatchStatus(status: WatchType) {
|
fun updateWatchStatus(
|
||||||
updateWatchStatus(currentResponse ?: return, status)
|
status: WatchType,
|
||||||
|
context: Context?,
|
||||||
|
loadResponse: LoadResponse? = null,
|
||||||
|
statusChangedCallback: ((statusChanged: Boolean) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val response = loadResponse ?: currentResponse ?: return
|
||||||
|
|
||||||
|
val currentId = response.getId()
|
||||||
|
|
||||||
|
val currentStatus = getResultWatchState(currentId)
|
||||||
|
|
||||||
|
// If the current status is "NONE" and the new status is not "NONE",
|
||||||
|
// fetch the bookmarked data to check for duplicates, otherwise set this
|
||||||
|
// to an empty list, so that we don't show the duplicate warning dialog,
|
||||||
|
// but we still want to update the current bookmark and refresh the data anyway.
|
||||||
|
val bookmarkedData = if (currentStatus == WatchType.NONE && status != WatchType.NONE) {
|
||||||
|
getAllBookmarkedData()
|
||||||
|
} else emptyList()
|
||||||
|
|
||||||
|
checkAndWarnDuplicates(
|
||||||
|
context,
|
||||||
|
LibraryListType.BOOKMARKS,
|
||||||
|
CheckDuplicateData(
|
||||||
|
name = response.name,
|
||||||
|
year = response.year,
|
||||||
|
syncData = response.syncData,
|
||||||
|
),
|
||||||
|
bookmarkedData
|
||||||
|
) { shouldContinue: Boolean, duplicateIds: List<Int?> ->
|
||||||
|
if (!shouldContinue) return@checkAndWarnDuplicates
|
||||||
|
|
||||||
|
if (duplicateIds.isNotEmpty()) {
|
||||||
|
duplicateIds.forEach { duplicateId ->
|
||||||
|
deleteBookmarkedData(duplicateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setResultWatchState(currentId, status.internalId)
|
||||||
|
|
||||||
|
// We don't need to store if WatchType.NONE.
|
||||||
|
// The key is removed in setResultWatchState, we don't want to
|
||||||
|
// re-add it again here if it was just removed.
|
||||||
|
if (status != WatchType.NONE) {
|
||||||
|
val current = getBookmarkedData(currentId)
|
||||||
|
|
||||||
|
setBookmarkedData(
|
||||||
|
currentId,
|
||||||
|
DataStoreHelper.BookmarkedData(
|
||||||
|
current?.bookmarkedTime ?: unixTimeMS,
|
||||||
|
currentId,
|
||||||
|
unixTimeMS,
|
||||||
|
response.name,
|
||||||
|
response.url,
|
||||||
|
response.apiName,
|
||||||
|
response.type,
|
||||||
|
response.posterUrl,
|
||||||
|
response.year,
|
||||||
|
response.syncData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStatus != status) {
|
||||||
|
MainActivity.bookmarksUpdatedEvent(true)
|
||||||
|
}
|
||||||
|
|
||||||
_watchStatus.postValue(status)
|
_watchStatus.postValue(status)
|
||||||
|
|
||||||
|
statusChangedCallback?.invoke(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startChromecast(
|
private fun startChromecast(
|
||||||
|
@ -839,73 +910,255 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the new status is Subscribed, false if not. Null if not possible to subscribe.
|
* Toggles the subscription status of an item.
|
||||||
**/
|
*
|
||||||
fun toggleSubscriptionStatus(): Boolean? {
|
* @param context The context to use for operations.
|
||||||
val isSubscribed = _subscribeStatus.value ?: return null
|
* @param statusChangedCallback A callback that is invoked when the subscription status changes.
|
||||||
val response = currentResponse ?: return null
|
* It provides the new subscription status (true if subscribed, false if unsubscribed, null if action was canceled).
|
||||||
if (response !is EpisodeResponse) return null
|
*/
|
||||||
|
fun toggleSubscriptionStatus(
|
||||||
|
context: Context?,
|
||||||
|
statusChangedCallback: ((newStatus: Boolean?) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val isSubscribed = _subscribeStatus.value ?: return
|
||||||
|
val response = currentResponse ?: return
|
||||||
|
if (response !is EpisodeResponse) return
|
||||||
|
|
||||||
val currentId = response.getId()
|
val currentId = response.getId()
|
||||||
|
|
||||||
if (isSubscribed) {
|
if (isSubscribed) {
|
||||||
DataStoreHelper.removeSubscribedData(currentId)
|
removeSubscribedData(currentId)
|
||||||
|
statusChangedCallback?.invoke(false)
|
||||||
|
_subscribeStatus.postValue(false)
|
||||||
} else {
|
} else {
|
||||||
val current = DataStoreHelper.getSubscribedData(currentId)
|
checkAndWarnDuplicates(
|
||||||
|
context,
|
||||||
|
LibraryListType.SUBSCRIPTIONS,
|
||||||
|
CheckDuplicateData(
|
||||||
|
name = response.name,
|
||||||
|
year = response.year,
|
||||||
|
syncData = response.syncData,
|
||||||
|
),
|
||||||
|
getAllSubscriptions(),
|
||||||
|
) { shouldContinue: Boolean, duplicateIds: List<Int?> ->
|
||||||
|
if (!shouldContinue) {
|
||||||
|
statusChangedCallback?.invoke(null)
|
||||||
|
return@checkAndWarnDuplicates
|
||||||
|
}
|
||||||
|
|
||||||
DataStoreHelper.setSubscribedData(
|
if (duplicateIds.isNotEmpty()) {
|
||||||
|
duplicateIds.forEach { duplicateId ->
|
||||||
|
removeSubscribedData(duplicateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val current = getSubscribedData(currentId)
|
||||||
|
|
||||||
|
setSubscribedData(
|
||||||
currentId,
|
currentId,
|
||||||
DataStoreHelper.SubscribedData(
|
DataStoreHelper.SubscribedData(
|
||||||
currentId,
|
current?.subscribedTime ?: unixTimeMS,
|
||||||
current?.bookmarkedTime ?: unixTimeMS,
|
|
||||||
unixTimeMS,
|
|
||||||
response.getLatestEpisodes(),
|
response.getLatestEpisodes(),
|
||||||
|
currentId,
|
||||||
|
unixTimeMS,
|
||||||
response.name,
|
response.name,
|
||||||
response.url,
|
response.url,
|
||||||
response.apiName,
|
response.apiName,
|
||||||
response.type,
|
response.type,
|
||||||
response.posterUrl,
|
response.posterUrl,
|
||||||
response.year
|
response.year,
|
||||||
|
response.syncData
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
_subscribeStatus.postValue(!isSubscribed)
|
_subscribeStatus.postValue(true)
|
||||||
return !isSubscribed
|
|
||||||
|
statusChangedCallback?.invoke(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if added to favorites, false if not. Null if not possible to favorite.
|
* Toggles the favorite status of an item.
|
||||||
**/
|
*
|
||||||
fun toggleFavoriteStatus(): Boolean? {
|
* @param context The context to use.
|
||||||
val isFavorite = _favoriteStatus.value ?: return null
|
* @param statusChangedCallback A callback that is invoked when the favorite status changes.
|
||||||
val response = currentResponse ?: return null
|
* It provides the new favorite status (true if added to favorites, false if removed, null if action was canceled).
|
||||||
|
*/
|
||||||
|
fun toggleFavoriteStatus(
|
||||||
|
context: Context?,
|
||||||
|
statusChangedCallback: ((newStatus: Boolean?) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val isFavorite = _favoriteStatus.value ?: return
|
||||||
|
val response = currentResponse ?: return
|
||||||
|
|
||||||
val currentId = response.getId()
|
val currentId = response.getId()
|
||||||
|
|
||||||
if (isFavorite) {
|
if (isFavorite) {
|
||||||
removeFavoritesData(currentId)
|
removeFavoritesData(currentId)
|
||||||
|
statusChangedCallback?.invoke(false)
|
||||||
|
_favoriteStatus.postValue(false)
|
||||||
} else {
|
} else {
|
||||||
|
checkAndWarnDuplicates(
|
||||||
|
context,
|
||||||
|
LibraryListType.FAVORITES,
|
||||||
|
CheckDuplicateData(
|
||||||
|
name = response.name,
|
||||||
|
year = response.year,
|
||||||
|
syncData = response.syncData,
|
||||||
|
),
|
||||||
|
getAllFavorites(),
|
||||||
|
) { shouldContinue: Boolean, duplicateIds: List<Int?> ->
|
||||||
|
if (!shouldContinue) {
|
||||||
|
statusChangedCallback?.invoke(null)
|
||||||
|
return@checkAndWarnDuplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateIds.isNotEmpty()) {
|
||||||
|
duplicateIds.forEach { duplicateId ->
|
||||||
|
removeFavoritesData(duplicateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val current = getFavoritesData(currentId)
|
val current = getFavoritesData(currentId)
|
||||||
|
|
||||||
setFavoritesData(
|
setFavoritesData(
|
||||||
currentId,
|
currentId,
|
||||||
DataStoreHelper.FavoritesData(
|
DataStoreHelper.FavoritesData(
|
||||||
currentId,
|
|
||||||
current?.favoritesTime ?: unixTimeMS,
|
current?.favoritesTime ?: unixTimeMS,
|
||||||
|
currentId,
|
||||||
unixTimeMS,
|
unixTimeMS,
|
||||||
response.name,
|
response.name,
|
||||||
response.url,
|
response.url,
|
||||||
response.apiName,
|
response.apiName,
|
||||||
response.type,
|
response.type,
|
||||||
response.posterUrl,
|
response.posterUrl,
|
||||||
response.year
|
response.year,
|
||||||
|
response.syncData
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_favoriteStatus.postValue(true)
|
||||||
|
|
||||||
|
statusChangedCallback?.invoke(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_favoriteStatus.postValue(!isFavorite)
|
@MainThread
|
||||||
return !isFavorite
|
private fun checkAndWarnDuplicates(
|
||||||
|
context: Context?,
|
||||||
|
listType: LibraryListType,
|
||||||
|
checkDuplicateData: CheckDuplicateData,
|
||||||
|
data: List<DataStoreHelper.LibrarySearchResponse>,
|
||||||
|
checkDuplicatesCallback: (shouldContinue: Boolean, duplicateIds: List<Int?>) -> Unit
|
||||||
|
) {
|
||||||
|
val whitespaceRegex = "\\s+".toRegex()
|
||||||
|
fun normalizeString(input: String): String {
|
||||||
|
/**
|
||||||
|
* Trim the input string and replace consecutive spaces with a single space.
|
||||||
|
* This covers some edge-cases where the title does not match exactly across providers,
|
||||||
|
* and one provider has the title with an extra whitespace. This is minor enough that
|
||||||
|
* it should still match in this case.
|
||||||
|
*/
|
||||||
|
return input.trim().replace(whitespaceRegex, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
val syncData = checkDuplicateData.syncData
|
||||||
|
|
||||||
|
val imdbId = getImdbIdFromSyncData(syncData)
|
||||||
|
val tmdbId = getTMDbIdFromSyncData(syncData)
|
||||||
|
val malId = syncData?.get(AccountManager.malApi.idPrefix)
|
||||||
|
val aniListId = syncData?.get(AccountManager.aniListApi.idPrefix)
|
||||||
|
val normalizedName = normalizeString(checkDuplicateData.name)
|
||||||
|
val year = checkDuplicateData.year
|
||||||
|
|
||||||
|
val duplicateEntries = data.filter { it: DataStoreHelper.LibrarySearchResponse ->
|
||||||
|
val librarySyncData = it.syncData
|
||||||
|
|
||||||
|
val checks = listOf(
|
||||||
|
{ imdbId != null && getImdbIdFromSyncData(librarySyncData) == imdbId },
|
||||||
|
{ tmdbId != null && getTMDbIdFromSyncData(librarySyncData) == tmdbId },
|
||||||
|
{ malId != null && librarySyncData?.get(AccountManager.malApi.idPrefix) == malId },
|
||||||
|
{ aniListId != null && librarySyncData?.get(AccountManager.aniListApi.idPrefix) == aniListId },
|
||||||
|
{ normalizedName == normalizeString(it.name) && year == it.year }
|
||||||
|
)
|
||||||
|
|
||||||
|
checks.any { it() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateEntries.isEmpty() || context == null) {
|
||||||
|
checkDuplicatesCallback.invoke(true, emptyList())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val replaceMessage = if (duplicateEntries.size > 1) {
|
||||||
|
R.string.duplicate_replace_all
|
||||||
|
} else R.string.duplicate_replace
|
||||||
|
|
||||||
|
val message = if (duplicateEntries.size == 1) {
|
||||||
|
val list = when (listType) {
|
||||||
|
LibraryListType.BOOKMARKS -> getResultWatchState(duplicateEntries[0].id ?: 0).stringRes
|
||||||
|
LibraryListType.FAVORITES -> R.string.favorites_list_name
|
||||||
|
LibraryListType.SUBSCRIPTIONS -> R.string.subscription_list_name
|
||||||
|
}
|
||||||
|
|
||||||
|
context.getString(R.string.duplicate_message_single,
|
||||||
|
"${normalizeString(duplicateEntries[0].name)} (${context.getString(list)}) — ${duplicateEntries[0].apiName}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val bulletPoints = duplicateEntries.joinToString("\n") {
|
||||||
|
val list = when (listType) {
|
||||||
|
LibraryListType.BOOKMARKS -> getResultWatchState(it.id ?: 0).stringRes
|
||||||
|
LibraryListType.FAVORITES -> R.string.favorites_list_name
|
||||||
|
LibraryListType.SUBSCRIPTIONS -> R.string.subscription_list_name
|
||||||
|
}
|
||||||
|
|
||||||
|
"• ${it.apiName}: ${normalizeString(it.name)} (${context.getString(list)})"
|
||||||
|
}
|
||||||
|
|
||||||
|
context.getString(R.string.duplicate_message_multiple, bulletPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
||||||
|
|
||||||
|
val dialogClickListener =
|
||||||
|
DialogInterface.OnClickListener { _, which ->
|
||||||
|
when (which) {
|
||||||
|
DialogInterface.BUTTON_POSITIVE -> {
|
||||||
|
checkDuplicatesCallback.invoke(true, emptyList())
|
||||||
|
}
|
||||||
|
DialogInterface.BUTTON_NEGATIVE -> {
|
||||||
|
checkDuplicatesCallback.invoke(false, emptyList())
|
||||||
|
}
|
||||||
|
DialogInterface.BUTTON_NEUTRAL -> {
|
||||||
|
checkDuplicatesCallback.invoke(true, duplicateEntries.map { it.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setTitle(R.string.duplicate_title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.duplicate_add, dialogClickListener)
|
||||||
|
.setNegativeButton(R.string.duplicate_cancel, dialogClickListener)
|
||||||
|
.setNeutralButton(replaceMessage, dialogClickListener)
|
||||||
|
.show().setDefaultFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImdbIdFromSyncData(syncData: Map<String, String>?): String? {
|
||||||
|
return normalSafeApiCall {
|
||||||
|
SimklApi.readIdFromString(
|
||||||
|
syncData?.get(AccountManager.simklApi.idPrefix)
|
||||||
|
)[SimklApi.Companion.SyncServices.Imdb]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTMDbIdFromSyncData(syncData: Map<String, String>?): String? {
|
||||||
|
return normalSafeApiCall {
|
||||||
|
SimklApi.readIdFromString(
|
||||||
|
syncData?.get(AccountManager.simklApi.idPrefix)
|
||||||
|
)[SimklApi.Companion.SyncServices.Tmdb]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startChromecast(
|
private fun startChromecast(
|
||||||
|
@ -1259,7 +1512,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
// Do not add mark as watched on movies
|
// Do not add mark as watched on movies
|
||||||
if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) {
|
if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) {
|
||||||
val isWatched =
|
val isWatched =
|
||||||
DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
||||||
|
|
||||||
val watchedText = if (isWatched) R.string.action_remove_from_watched
|
val watchedText = if (isWatched) R.string.action_remove_from_watched
|
||||||
else R.string.action_mark_as_watched
|
else R.string.action_mark_as_watched
|
||||||
|
@ -1508,12 +1761,12 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
ACTION_MARK_AS_WATCHED -> {
|
ACTION_MARK_AS_WATCHED -> {
|
||||||
val isWatched =
|
val isWatched =
|
||||||
DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
||||||
|
|
||||||
if (isWatched) {
|
if (isWatched) {
|
||||||
DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.None)
|
setVideoWatchState(click.data.id, VideoWatchState.None)
|
||||||
} else {
|
} else {
|
||||||
DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.Watched)
|
setVideoWatchState(click.data.id, VideoWatchState.Watched)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kinda dirty to reload all episodes :(
|
// Kinda dirty to reload all episodes :(
|
||||||
|
@ -1722,7 +1975,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
list.subList(start, end).map {
|
list.subList(start, end).map {
|
||||||
val posDur = getViewPos(it.id)
|
val posDur = getViewPos(it.id)
|
||||||
val watchState =
|
val watchState =
|
||||||
DataStoreHelper.getVideoWatchState(it.id) ?: VideoWatchState.None
|
getVideoWatchState(it.id) ?: VideoWatchState.None
|
||||||
it.copy(
|
it.copy(
|
||||||
position = posDur?.position ?: 0,
|
position = posDur?.position ?: 0,
|
||||||
duration = posDur?.duration ?: 0,
|
duration = posDur?.duration ?: 0,
|
||||||
|
@ -1783,8 +2036,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private fun postSubscription(loadResponse: LoadResponse) {
|
private fun postSubscription(loadResponse: LoadResponse) {
|
||||||
if (loadResponse.isEpisodeBased()) {
|
if (loadResponse.isEpisodeBased()) {
|
||||||
val id = loadResponse.getId()
|
val id = loadResponse.getId()
|
||||||
val data = DataStoreHelper.getSubscribedData(id)
|
val data = getSubscribedData(id)
|
||||||
DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
|
updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
|
||||||
val isSubscribed = data != null
|
val isSubscribed = data != null
|
||||||
_subscribeStatus.postValue(isSubscribed)
|
_subscribeStatus.postValue(isSubscribed)
|
||||||
}
|
}
|
||||||
|
@ -2162,13 +2415,13 @@ class ResultViewModel2 : ViewModel() {
|
||||||
postResume()
|
postResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postResume() {
|
private fun postResume() {
|
||||||
_resumeWatching.postValue(resume())
|
_resumeWatching.postValue(resume())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resume(): ResumeWatchingStatus? {
|
private fun resume(): ResumeWatchingStatus? {
|
||||||
val correctId = currentId ?: return null
|
val correctId = currentId ?: return null
|
||||||
val resume = DataStoreHelper.getLastWatched(correctId)
|
val resume = getLastWatched(correctId)
|
||||||
val resumeParentId = resume?.parentId
|
val resumeParentId = resume?.parentId
|
||||||
if (resumeParentId != correctId) return null // is null or smth went wrong with getLastWatched
|
if (resumeParentId != correctId) return null // is null or smth went wrong with getLastWatched
|
||||||
val resumeId = resume.episodeId ?: return null// invalid episode id
|
val resumeId = resume.episodeId ?: return null// invalid episode id
|
||||||
|
|
|
@ -352,20 +352,35 @@ 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 LibrarySearchResponse(
|
||||||
@JsonProperty("id") override var id: Int?,
|
@JsonProperty("id") override var id: Int?,
|
||||||
@JsonProperty("subscribedTime") val bookmarkedTime: Long,
|
@JsonProperty("latestUpdatedTime") open val latestUpdatedTime: 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?,
|
||||||
@JsonProperty("posterUrl") override var posterUrl: String?,
|
@JsonProperty("posterUrl") override var posterUrl: String?,
|
||||||
@JsonProperty("year") val year: Int?,
|
@JsonProperty("year") open val year: Int?,
|
||||||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
@JsonProperty("syncData") open val syncData: Map<String, String>?,
|
||||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
@JsonProperty("quality") override var quality: SearchQuality?,
|
||||||
) : SearchResponse {
|
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>?
|
||||||
|
) : SearchResponse
|
||||||
|
|
||||||
|
data class SubscribedData(
|
||||||
|
@JsonProperty("subscribedTime") val subscribedTime: Long,
|
||||||
|
@JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map<DubStatus, Int?>,
|
||||||
|
override var id: Int?,
|
||||||
|
override val latestUpdatedTime: Long,
|
||||||
|
override val name: String,
|
||||||
|
override val url: String,
|
||||||
|
override val apiName: String,
|
||||||
|
override var type: TvType?,
|
||||||
|
override var posterUrl: String?,
|
||||||
|
override val year: Int?,
|
||||||
|
override val syncData: Map<String, String>? = null,
|
||||||
|
override var quality: SearchQuality? = null,
|
||||||
|
override var posterHeaders: Map<String, String>? = null
|
||||||
|
) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
|
||||||
fun toLibraryItem(): SyncAPI.LibraryItem? {
|
fun toLibraryItem(): SyncAPI.LibraryItem? {
|
||||||
return SyncAPI.LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
name,
|
name,
|
||||||
|
@ -381,18 +396,19 @@ 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,
|
override var id: Int?,
|
||||||
@JsonProperty("name") override val name: String,
|
override val latestUpdatedTime: Long,
|
||||||
@JsonProperty("url") override val url: String,
|
override val name: String,
|
||||||
@JsonProperty("apiName") override val apiName: String,
|
override val url: String,
|
||||||
@JsonProperty("type") override var type: TvType? = null,
|
override val apiName: String,
|
||||||
@JsonProperty("posterUrl") override var posterUrl: String?,
|
override var type: TvType?,
|
||||||
@JsonProperty("year") val year: Int?,
|
override var posterUrl: String?,
|
||||||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
override val year: Int?,
|
||||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
override val syncData: Map<String, String>? = null,
|
||||||
) : SearchResponse {
|
override var quality: SearchQuality? = null,
|
||||||
|
override var posterHeaders: Map<String, String>? = null
|
||||||
|
) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
|
||||||
fun toLibraryItem(id: String): SyncAPI.LibraryItem {
|
fun toLibraryItem(id: String): SyncAPI.LibraryItem {
|
||||||
return SyncAPI.LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
name,
|
name,
|
||||||
|
@ -408,18 +424,19 @@ 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,
|
override var id: Int?,
|
||||||
@JsonProperty("name") override val name: String,
|
override val latestUpdatedTime: Long,
|
||||||
@JsonProperty("url") override val url: String,
|
override val name: String,
|
||||||
@JsonProperty("apiName") override val apiName: String,
|
override val url: String,
|
||||||
@JsonProperty("type") override var type: TvType? = null,
|
override val apiName: String,
|
||||||
@JsonProperty("posterUrl") override var posterUrl: String?,
|
override var type: TvType?,
|
||||||
@JsonProperty("year") val year: Int?,
|
override var posterUrl: String?,
|
||||||
@JsonProperty("quality") override var quality: SearchQuality? = null,
|
override val year: Int?,
|
||||||
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
|
override val syncData: Map<String, String>? = null,
|
||||||
) : SearchResponse {
|
override var quality: SearchQuality? = null,
|
||||||
|
override var posterHeaders: Map<String, String>? = null
|
||||||
|
) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
|
||||||
fun toLibraryItem(): SyncAPI.LibraryItem? {
|
fun toLibraryItem(): SyncAPI.LibraryItem? {
|
||||||
return SyncAPI.LibraryItem(
|
return SyncAPI.LibraryItem(
|
||||||
name,
|
name,
|
||||||
|
@ -572,6 +589,12 @@ object DataStoreHelper {
|
||||||
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())
|
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllBookmarkedData(): List<BookmarkedData> {
|
||||||
|
return getKeys("$currentAccount/$RESULT_WATCH_STATE_DATA")?.mapNotNull {
|
||||||
|
getKey(it)
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllSubscriptions(): List<SubscribedData> {
|
fun getAllSubscriptions(): List<SubscribedData> {
|
||||||
return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull {
|
return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull {
|
||||||
getKey(it)
|
getKey(it)
|
||||||
|
|
|
@ -686,13 +686,32 @@
|
||||||
<string name="qualities">Qualities</string>
|
<string name="qualities">Qualities</string>
|
||||||
<string name="profile_background_des">Profile background</string>
|
<string name="profile_background_des">Profile background</string>
|
||||||
<string name="unable_to_inflate">UI was unable to be created correctly, this is a MAJOR BUG and should be reported immediately %s</string>
|
<string name="unable_to_inflate">UI was unable to be created correctly, this is a MAJOR BUG and should be reported immediately %s</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="tv_no_focus_tag" translatable="false">tv_no_focus_tag</string>
|
|
||||||
<string name="already_voted">You have already voted</string>
|
<string name="already_voted">You have already voted</string>
|
||||||
|
|
||||||
<string name="favorites_list_name">Favorites</string>
|
<string name="favorites_list_name">Favorites</string>
|
||||||
<string name="favorite_added">%s added to favorites</string>
|
<string name="favorite_added">%s added to favorites</string>
|
||||||
<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="duplicate_title">Potential Duplicate Found</string>
|
||||||
|
<string name="duplicate_add">Add</string>
|
||||||
|
<string name="duplicate_replace">Replace</string>
|
||||||
|
<string name="duplicate_replace_all">Replace All</string>
|
||||||
|
<string name="duplicate_cancel" translatable="false">@string/sort_cancel</string>
|
||||||
|
<string name="duplicate_message_single">
|
||||||
|
It appears that a potentially duplicate item already exists in your library: \'%1$s.\'
|
||||||
|
|
||||||
|
\n\nWould you like to add this item anyway, replace the existing one, or cancel the action?
|
||||||
|
</string>
|
||||||
|
<string name="duplicate_message_multiple">
|
||||||
|
Potential duplicate items have been found in your library:
|
||||||
|
|
||||||
|
\n\n%1$s
|
||||||
|
|
||||||
|
\n\nWould you like to add this item anyway, replace the existing ones, or cancel the action?
|
||||||
|
</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="tv_no_focus_tag" translatable="false">tv_no_focus_tag</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue