From d77cc8f98d9bc326c63b3352a18c09526d89594f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:09:05 -0600 Subject: [PATCH] Support multiple duplicates, cleanup, and other fixes --- .../ui/result/ResultViewModel2.kt | 143 ++++++++++++------ .../cloudstream3/utils/DataStoreHelper.kt | 6 +- app/src/main/res/values/strings.xml | 16 +- 3 files changed, 107 insertions(+), 58 deletions(-) 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 e90a94e6..fa8cf386 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 @@ -7,6 +7,7 @@ import android.os.Build import android.os.Bundle import android.util.Log import android.widget.Toast +import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider import androidx.core.net.toUri @@ -130,6 +131,18 @@ data class ResultData( val plotHeaderText: UiText, ) +data class CheckDuplicateData( + val name: String, + val year: Int?, + val imdbId: String? +) + +enum class LibraryListType { + BOOKMARKS, + FAVORITES, + SUBSCRIPTIONS +} + fun txt(status: DubStatus?): UiText? { return txt( when (status) { @@ -824,20 +837,29 @@ class ResultViewModel2 : ViewModel() { val currentStatus = getResultWatchState(currentId) - if (status == currentStatus) return + // If the status is not actually changing, set this to an empty + // list so that we don't show the duplicate warning dialog, but we + // still want to refresh the data anyway + val bookmarkedData = if (status == currentStatus) { + emptyList() + } else getBookmarkedDataByWatchType(status) checkAndWarnDuplicates( context, - R.string.duplicate_message_bookmarks, - response.name, - response.year, - response.getImdbId(), - getBookmarkedDataByWatchType(status) - ) { shouldContinue: Boolean, duplicateId: Int? -> + LibraryListType.BOOKMARKS, + CheckDuplicateData( + name = response.name, + year = response.year, + imdbId = response.getImdbId(), + ), + bookmarkedData + ) { shouldContinue: Boolean, duplicateIds: List -> if (!shouldContinue) return@checkAndWarnDuplicates - if (duplicateId != null) { - deleteBookmarkedData(duplicateId) + if (duplicateIds.isNotEmpty()) { + duplicateIds.forEach { duplicateId -> + deleteBookmarkedData(duplicateId) + } } setResultWatchState(currentId, status.internalId) @@ -900,19 +922,23 @@ class ResultViewModel2 : ViewModel() { } else { checkAndWarnDuplicates( context, - R.string.duplicate_message_subscriptions, - response.name, - response.year, - response.getImdbId(), + LibraryListType.SUBSCRIPTIONS, + CheckDuplicateData( + name = response.name, + year = response.year, + imdbId = response.getImdbId(), + ), getAllSubscriptions(), - ) { shouldContinue: Boolean, duplicateId: Int? -> + ) { shouldContinue: Boolean, duplicateIds: List -> if (!shouldContinue) { statusChangedCallback?.invoke(null) return@checkAndWarnDuplicates } - if (duplicateId != null) { - removeSubscribedData(duplicateId) + if (duplicateIds.isNotEmpty()) { + duplicateIds.forEach { duplicateId -> + removeSubscribedData(duplicateId) + } } val current = getSubscribedData(currentId) @@ -963,19 +989,23 @@ class ResultViewModel2 : ViewModel() { } else { checkAndWarnDuplicates( context, - R.string.duplicate_message_favorites, - response.name, - response.year, - response.getImdbId(), + LibraryListType.FAVORITES, + CheckDuplicateData( + name = response.name, + year = response.year, + imdbId = response.getImdbId(), + ), getAllFavorites(), - ) { shouldContinue: Boolean, duplicateId: Int? -> + ) { shouldContinue: Boolean, duplicateIds: List -> if (!shouldContinue) { statusChangedCallback?.invoke(null) return@checkAndWarnDuplicates } - if (duplicateId != null) { - removeFavoritesData(duplicateId) + if (duplicateIds.isNotEmpty()) { + duplicateIds.forEach { duplicateId -> + removeFavoritesData(duplicateId) + } } val current = getFavoritesData(currentId) @@ -1005,14 +1035,13 @@ class ResultViewModel2 : ViewModel() { } } + @MainThread private fun checkAndWarnDuplicates( context: Context?, - message: Int, - name: String, - year: Int?, - imdbId: String?, + listType: LibraryListType, + checkDuplicateData: CheckDuplicateData, data: List, - checkDuplicatesCallback: (shouldContinue: Boolean, duplicateId: Int?) -> Unit + checkDuplicatesCallback: (shouldContinue: Boolean, duplicateIds: List) -> Unit ) { fun normalizeString(input: String): String { /** @@ -1021,48 +1050,70 @@ class ResultViewModel2 : ViewModel() { * 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("\\s+".toRegex(), " ") + val regex = "\\s+".toRegex() + return input.trim().replace(regex, " ") } - val duplicateEntry = data.find { - imdbId != null && it.imdbId == imdbId || - normalizeString(it.name) == normalizeString(name) && it.year == year + val duplicateEntries = data.filter { + checkDuplicateData.imdbId != null && it.imdbId == checkDuplicateData.imdbId || + normalizeString(it.name) == normalizeString(checkDuplicateData.name) && it.year == checkDuplicateData.year } - if (duplicateEntry == null || context == null) { - checkDuplicatesCallback.invoke(true, null) + 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)})" + ) + } 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 + } + + "• ${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, null) + checkDuplicatesCallback.invoke(true, emptyList()) } DialogInterface.BUTTON_NEGATIVE -> { - checkDuplicatesCallback.invoke(false, null) + checkDuplicatesCallback.invoke(false, emptyList()) } DialogInterface.BUTTON_NEUTRAL -> { - checkDuplicatesCallback.invoke(true, duplicateEntry.id) + checkDuplicatesCallback.invoke(true, duplicateEntries.map { it.id }) } } } builder.setTitle(R.string.duplicate_title) - .setMessage( - context.getString(message).format( - normalizeString(duplicateEntry.name), - context.getString( - getResultWatchState(duplicateEntry.id ?: 0).stringRes - ) - ) - ) + .setMessage(message) .setPositiveButton(R.string.duplicate_add, dialogClickListener) .setNegativeButton(R.string.duplicate_cancel, dialogClickListener) - .setNeutralButton(R.string.duplicate_replace, dialogClickListener) + .setNeutralButton(replaceMessage, dialogClickListener) .show().setDefaultFocus() } 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 dc332729..0ef99881 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -377,7 +377,7 @@ object DataStoreHelper { override var type: TvType?, override var posterUrl: String?, override val year: Int?, - override val imdbId: String?, + override val imdbId: String? = null, override var quality: SearchQuality? = null, override var posterHeaders: Map? = null ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, imdbId, quality, posterHeaders) { @@ -405,7 +405,7 @@ object DataStoreHelper { override var type: TvType?, override var posterUrl: String?, override val year: Int?, - override val imdbId: String?, + override val imdbId: String? = null, override var quality: SearchQuality? = null, override var posterHeaders: Map? = null ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, imdbId, quality, posterHeaders) { @@ -433,7 +433,7 @@ object DataStoreHelper { override var type: TvType?, override var posterUrl: String?, override val year: Int?, - override val imdbId: String?, + override val imdbId: String? = null, override var quality: SearchQuality? = null, override var posterHeaders: Map? = null ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, imdbId, quality, posterHeaders) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14418d49..e243fe79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -697,21 +697,19 @@ Potential Duplicate Found Add Replace + Replace All @string/sort_cancel - - It appears that a potentially duplicate title \'%s\' with the watch status \'%s\' already exists in your library. + + 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? - - You are already subscribed to a title that appears to be a potential duplicate: \'%s.\' + + Potential duplicate items have been found in your library: - \n\nWould you like to add this subscription anyway, replace the existing one, or cancel the action? - - - It seems that a potentially duplicate title \'%s\' is already in your favorites. + \n\n%1$s - \n\nWould you like to add this item anyway, replace the existing one, or cancel the action? + \n\nWould you like to add this item anyway, replace the existing ones, or cancel the action?