From e4ba8520076b84d7b9d219852b2aabc8e92cf034 Mon Sep 17 00:00:00 2001
From: Luna712 <142361265+Luna712@users.noreply.github.com>
Date: Wed, 25 Oct 2023 08:43:29 -0600
Subject: [PATCH 1/4] Use bottom dialogs for synopsis (#709)
* Use bottom dialogs for synopsis
* Add bottomTextDialog
---
.../ui/result/ResultFragmentPhone.kt | 32 ++++++++----------
.../utils/SingleSelectionHelper.kt | 22 +++++++++++++
.../main/res/layout/bottom_text_dialog.xml | 33 +++++++++++++++++++
3 files changed, 68 insertions(+), 19 deletions(-)
create mode 100644 app/src/main/res/layout/bottom_text_dialog.xml
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 a0d82062..ed9fd270 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
@@ -17,7 +17,6 @@ import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
@@ -66,6 +65,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
@@ -681,14 +681,13 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultPoster.setImage(d.posterImage)
resultPosterBackground.setImage(d.posterBackgroundImage)
resultDescription.setTextHtml(d.plotText)
- resultDescription.setOnClickListener { view ->
- // todo bottom view?
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(d.plotText.asString(ctx).html())
- .setTitle(d.plotHeaderText.asString(ctx))
- .show()
+ resultDescription.setOnClickListener {
+ activity?.let { activity ->
+ activity.showBottomDialogText(
+ d.titleText.asString(activity),
+ d.plotText.asString(activity).html(),
+ {}
+ )
}
}
@@ -879,16 +878,11 @@ open class ResultFragmentPhone : FullScreenPlayer() {
setRecommendations(recommendations, null)
}
observe(viewModel.episodeSynopsis) { description ->
- // TODO bottom dialog
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(description.html())
- .setTitle(R.string.synopsis)
- .setOnDismissListener {
- viewModel.releaseEpisodeSynopsis()
- }
- .show()
+ activity?.let { activity ->
+ activity.showBottomDialogText(
+ activity.getString(R.string.synopsis),
+ description.html()
+ ) { viewModel.releaseEpisodeSynopsis() }
}
}
context?.let { ctx ->
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
index 8285b8ab..5d54ffe5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
@@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
import android.app.Activity
import android.app.Dialog
+import android.text.Spanned
import android.view.LayoutInflater
import android.view.View
import android.widget.AbsListView
@@ -19,6 +20,7 @@ import androidx.core.view.marginRight
import androidx.core.view.marginTop
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.R
+import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
@@ -363,4 +365,24 @@ object SingleSelectionHelper {
dismissCallback
)
}
+
+ fun Activity.showBottomDialogText(
+ title: String,
+ text: Spanned,
+ dismissCallback: () -> Unit
+ ) {
+ val binding = BottomTextDialogBinding.inflate(layoutInflater)
+ val dialog = BottomSheetDialog(this)
+
+ dialog.setContentView(binding.root)
+
+ binding.dialogTitle.text = title
+ binding.dialogText.text = text
+
+ dialog.setOnDismissListener {
+ dismissCallback.invoke()
+ }
+
+ dialog.show()
+ }
}
diff --git a/app/src/main/res/layout/bottom_text_dialog.xml b/app/src/main/res/layout/bottom_text_dialog.xml
new file mode 100644
index 00000000..01b4834d
--- /dev/null
+++ b/app/src/main/res/layout/bottom_text_dialog.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
From 968bd59188df2138ff9b8aae1b8022191b40de5d Mon Sep 17 00:00:00 2001
From: Luna712 <142361265+Luna712@users.noreply.github.com>
Date: Wed, 25 Oct 2023 18:09:21 -0600
Subject: [PATCH 2/4] Warn of potential duplicates when adding to library
(#691)
---
.../com/lagradost/cloudstream3/MainAPI.kt | 12 +
.../lagradost/cloudstream3/MainActivity.kt | 2 +-
.../syncproviders/providers/SimklApi.kt | 2 +-
.../ui/home/HomeParentItemAdapterPreview.kt | 32 +-
.../ui/result/ResultFragmentPhone.kt | 48 +-
.../ui/result/ResultFragmentTv.kt | 22 +-
.../ui/result/ResultViewModel2.kt | 419 ++++++++++++++----
.../cloudstream3/utils/DataStoreHelper.kt | 85 ++--
app/src/main/res/values/strings.xml | 25 +-
9 files changed, 481 insertions(+), 166 deletions(-)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
index 5b674c4c..bfe86224 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
@@ -1246,6 +1246,18 @@ interface LoadResponse {
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?) {
this.syncData[malIdPrefix] = (id ?: return).toString()
this.addSimklId(SimklApi.Companion.SyncServices.Mal, id.toString())
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index a4d1c7b4..a41028bd 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -1306,7 +1306,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
this@MainActivity.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
- viewModel.updateWatchStatus(WatchType.values()[it])
+ viewModel.updateWatchStatus(WatchType.values()[it], this@MainActivity)
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
index cd1df562..bd7979f5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
@@ -203,7 +203,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
}
/** Read the id string to get all other ids */
- private fun readIdFromString(idString: String?): Map {
+ fun readIdFromString(idString: String?): Map {
return tryParseJson(idString) ?: return emptyMap()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
index b7e52b88..5f194f1f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
@@ -378,21 +378,25 @@ class HomeParentItemAdapterPreview(
showApply = false,
{}) {
val newValue = WatchType.values()[it]
- homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
- null,
- ContextCompat.getDrawable(
- homePreviewBookmark.context,
- newValue.iconRes
- ),
- null,
- null
- )
- homePreviewBookmark.setText(newValue.stringRes)
- ResultViewModel2.updateWatchStatus(
- item,
- newValue
- )
+ ResultViewModel2().updateWatchStatus(
+ newValue,
+ fab.context,
+ item
+ ) { statusChanged: Boolean ->
+ if (!statusChanged) return@updateWatchStatus
+
+ homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
+ null,
+ ContextCompat.getDrawable(
+ homePreviewBookmark.context,
+ newValue.iconRes
+ ),
+ null,
+ null
+ )
+ homePreviewBookmark.setText(newValue.stringRes)
+ }
}
}
}
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 ed9fd270..7bcce764 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
@@ -430,34 +430,36 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}
})
resultSubscribe.setOnClickListener {
- val isSubscribed =
- viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
+ viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
+ if (newStatus == null) return@toggleSubscriptionStatus
- val message = if (isSubscribed) {
- // Kinda icky to have this here, but it works.
- SubscriptionWorkManager.enqueuePeriodicWork(context)
- R.string.subscription_new
- } else {
- R.string.subscription_deleted
+ val message = if (newStatus) {
+ // Kinda icky to have this here, but it works.
+ SubscriptionWorkManager.enqueuePeriodicWork(context)
+ R.string.subscription_new
+ } else {
+ R.string.subscription_deleted
+ }
+
+ 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)
}
-
- 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)
}
resultFavorite.setOnClickListener {
- val isFavorite =
- viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
+ viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? ->
+ if (newStatus == null) return@toggleFavoriteStatus
- val message = if (isFavorite) {
- R.string.favorite_added
- } else {
- R.string.favorite_removed
+ val message = if (newStatus) {
+ 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)
}
-
- 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
@@ -960,7 +962,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
- viewModel.updateWatchStatus(WatchType.values()[it])
+ viewModel.updateWatchStatus(WatchType.values()[it], context)
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
index feb8ca04..c13854e0 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
@@ -531,7 +531,7 @@ class ResultFragmentTv : Fragment() {
view.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
- viewModel.updateWatchStatus(WatchType.values()[it])
+ viewModel.updateWatchStatus(WatchType.values()[it], context)
}
}
}
@@ -557,17 +557,19 @@ class ResultFragmentTv : Fragment() {
setIconResource(drawable)
setText(text)
setOnClickListener {
- val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
+ viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? ->
+ if (newStatus == null) return@toggleFavoriteStatus
- val message = if (isFavorite) {
- R.string.favorite_added
- } else {
- R.string.favorite_removed
+ val message = if (newStatus) {
+ 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)
}
-
- 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)
}
}
}
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 e5ed7b92..1631b706 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,8 @@ 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
import androidx.lifecycle.LiveData
@@ -31,6 +33,7 @@ import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI
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.WatchType
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.isAppInstalled
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.Coroutines.ioSafe
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.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.getFavoritesData
+import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
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.getSubscribedData
+import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
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.setFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
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 kotlinx.coroutines.*
import java.io.File
@@ -113,6 +131,18 @@ data class ResultData(
val plotHeaderText: UiText,
)
+data class CheckDuplicateData(
+ val name: String,
+ val year: Int?,
+ val syncData: Map?
+)
+
+enum class LibraryListType {
+ BOOKMARKS,
+ FAVORITES,
+ SUBSCRIPTIONS
+}
+
fun txt(status: DubStatus?): UiText? {
return txt(
when (status) {
@@ -441,33 +471,6 @@ class ResultViewModel2 : ViewModel() {
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? {
if (name == null) return null
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
@@ -822,9 +825,77 @@ class ResultViewModel2 : ViewModel() {
val selectPopup: LiveData = _selectPopup
- fun updateWatchStatus(status: WatchType) {
- updateWatchStatus(currentResponse ?: return, status)
- _watchStatus.postValue(status)
+ fun updateWatchStatus(
+ 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 ->
+ 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)
+
+ statusChangedCallback?.invoke(true)
+ }
}
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.
- **/
- fun toggleSubscriptionStatus(): Boolean? {
- val isSubscribed = _subscribeStatus.value ?: return null
- val response = currentResponse ?: return null
- if (response !is EpisodeResponse) return null
+ * Toggles the subscription status of an item.
+ *
+ * @param context The context to use for operations.
+ * @param statusChangedCallback A callback that is invoked when the subscription status changes.
+ * It provides the new subscription status (true if subscribed, false if unsubscribed, null if action was canceled).
+ */
+ 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()
if (isSubscribed) {
- DataStoreHelper.removeSubscribedData(currentId)
+ removeSubscribedData(currentId)
+ statusChangedCallback?.invoke(false)
+ _subscribeStatus.postValue(false)
} 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 ->
+ if (!shouldContinue) {
+ statusChangedCallback?.invoke(null)
+ return@checkAndWarnDuplicates
+ }
- DataStoreHelper.setSubscribedData(
- currentId,
- DataStoreHelper.SubscribedData(
+ if (duplicateIds.isNotEmpty()) {
+ duplicateIds.forEach { duplicateId ->
+ removeSubscribedData(duplicateId)
+ }
+ }
+
+ val current = getSubscribedData(currentId)
+
+ setSubscribedData(
currentId,
- current?.bookmarkedTime ?: unixTimeMS,
- unixTimeMS,
- response.getLatestEpisodes(),
- response.name,
- response.url,
- response.apiName,
- response.type,
- response.posterUrl,
- response.year
+ DataStoreHelper.SubscribedData(
+ current?.subscribedTime ?: unixTimeMS,
+ response.getLatestEpisodes(),
+ currentId,
+ unixTimeMS,
+ response.name,
+ response.url,
+ response.apiName,
+ response.type,
+ response.posterUrl,
+ response.year,
+ response.syncData
+ )
)
- )
- }
- _subscribeStatus.postValue(!isSubscribed)
- return !isSubscribed
+ _subscribeStatus.postValue(true)
+
+ statusChangedCallback?.invoke(true)
+ }
+ }
}
/**
- * @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
+ * Toggles the favorite status of an item.
+ *
+ * @param context The context to use.
+ * @param statusChangedCallback A callback that is invoked when the favorite status changes.
+ * 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()
if (isFavorite) {
removeFavoritesData(currentId)
+ statusChangedCallback?.invoke(false)
+ _favoriteStatus.postValue(false)
} else {
- val current = getFavoritesData(currentId)
+ checkAndWarnDuplicates(
+ context,
+ LibraryListType.FAVORITES,
+ CheckDuplicateData(
+ name = response.name,
+ year = response.year,
+ syncData = response.syncData,
+ ),
+ getAllFavorites(),
+ ) { shouldContinue: Boolean, duplicateIds: List ->
+ if (!shouldContinue) {
+ statusChangedCallback?.invoke(null)
+ return@checkAndWarnDuplicates
+ }
- setFavoritesData(
- currentId,
- DataStoreHelper.FavoritesData(
+ if (duplicateIds.isNotEmpty()) {
+ duplicateIds.forEach { duplicateId ->
+ removeFavoritesData(duplicateId)
+ }
+ }
+
+ val current = getFavoritesData(currentId)
+
+ setFavoritesData(
currentId,
- current?.favoritesTime ?: unixTimeMS,
- unixTimeMS,
- response.name,
- response.url,
- response.apiName,
- response.type,
- response.posterUrl,
- response.year
+ DataStoreHelper.FavoritesData(
+ current?.favoritesTime ?: unixTimeMS,
+ currentId,
+ unixTimeMS,
+ response.name,
+ response.url,
+ response.apiName,
+ response.type,
+ response.posterUrl,
+ response.year,
+ response.syncData
+ )
)
- )
+
+ _favoriteStatus.postValue(true)
+
+ statusChangedCallback?.invoke(true)
+ }
+ }
+ }
+
+ @MainThread
+ private fun checkAndWarnDuplicates(
+ context: Context?,
+ listType: LibraryListType,
+ checkDuplicateData: CheckDuplicateData,
+ data: List,
+ checkDuplicatesCallback: (shouldContinue: Boolean, duplicateIds: List) -> 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, " ")
}
- _favoriteStatus.postValue(!isFavorite)
- return !isFavorite
+ 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? {
+ return normalSafeApiCall {
+ SimklApi.readIdFromString(
+ syncData?.get(AccountManager.simklApi.idPrefix)
+ )[SimklApi.Companion.SyncServices.Imdb]
+ }
+ }
+
+ private fun getTMDbIdFromSyncData(syncData: Map?): String? {
+ return normalSafeApiCall {
+ SimklApi.readIdFromString(
+ syncData?.get(AccountManager.simklApi.idPrefix)
+ )[SimklApi.Companion.SyncServices.Tmdb]
+ }
}
private fun startChromecast(
@@ -1259,7 +1512,7 @@ class ResultViewModel2 : ViewModel() {
// Do not add mark as watched on movies
if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) {
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
else R.string.action_mark_as_watched
@@ -1508,12 +1761,12 @@ class ResultViewModel2 : ViewModel() {
ACTION_MARK_AS_WATCHED -> {
val isWatched =
- DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
+ getVideoWatchState(click.data.id) == VideoWatchState.Watched
if (isWatched) {
- DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.None)
+ setVideoWatchState(click.data.id, VideoWatchState.None)
} else {
- DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.Watched)
+ setVideoWatchState(click.data.id, VideoWatchState.Watched)
}
// Kinda dirty to reload all episodes :(
@@ -1722,7 +1975,7 @@ class ResultViewModel2 : ViewModel() {
list.subList(start, end).map {
val posDur = getViewPos(it.id)
val watchState =
- DataStoreHelper.getVideoWatchState(it.id) ?: VideoWatchState.None
+ getVideoWatchState(it.id) ?: VideoWatchState.None
it.copy(
position = posDur?.position ?: 0,
duration = posDur?.duration ?: 0,
@@ -1783,8 +2036,8 @@ class ResultViewModel2 : ViewModel() {
private fun postSubscription(loadResponse: LoadResponse) {
if (loadResponse.isEpisodeBased()) {
val id = loadResponse.getId()
- val data = DataStoreHelper.getSubscribedData(id)
- DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
+ val data = getSubscribedData(id)
+ updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
val isSubscribed = data != null
_subscribeStatus.postValue(isSubscribed)
}
@@ -2162,13 +2415,13 @@ class ResultViewModel2 : ViewModel() {
postResume()
}
- fun postResume() {
+ private fun postResume() {
_resumeWatching.postValue(resume())
}
private fun resume(): ResumeWatchingStatus? {
val correctId = currentId ?: return null
- val resume = DataStoreHelper.getLastWatched(correctId)
+ val resume = getLastWatched(correctId)
val resumeParentId = resume?.parentId
if (resumeParentId != correctId) return null // is null or smth went wrong with getLastWatched
val resumeId = resume.episodeId ?: return null// invalid episode id
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 4b4157d6..78f801b6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
@@ -352,20 +352,35 @@ object DataStoreHelper {
/**
* Used to display notifications on new episodes and posters in library.
**/
- data class SubscribedData(
+ abstract class LibrarySearchResponse(
@JsonProperty("id") override var id: Int?,
- @JsonProperty("subscribedTime") val bookmarkedTime: Long,
- @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
- @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map,
+ @JsonProperty("latestUpdatedTime") open 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("type") override var type: TvType?,
@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 {
+ @JsonProperty("year") open val year: Int?,
+ @JsonProperty("syncData") open val syncData: Map?,
+ @JsonProperty("quality") override var quality: SearchQuality?,
+ @JsonProperty("posterHeaders") override var posterHeaders: Map?
+ ) : SearchResponse
+
+ data class SubscribedData(
+ @JsonProperty("subscribedTime") val subscribedTime: Long,
+ @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map,
+ 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? = null,
+ override var quality: SearchQuality? = null,
+ override var posterHeaders: Map? = null
+ ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
fun toLibraryItem(): SyncAPI.LibraryItem? {
return SyncAPI.LibraryItem(
name,
@@ -381,18 +396,19 @@ object DataStoreHelper {
}
data class BookmarkedData(
- @JsonProperty("id") override var id: Int?,
@JsonProperty("bookmarkedTime") val bookmarkedTime: 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 {
+ 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? = null,
+ override var quality: SearchQuality? = null,
+ override var posterHeaders: Map? = null
+ ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
fun toLibraryItem(id: String): SyncAPI.LibraryItem {
return SyncAPI.LibraryItem(
name,
@@ -408,18 +424,19 @@ 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 {
+ 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? = null,
+ override var quality: SearchQuality? = null,
+ override var posterHeaders: Map? = null
+ ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders) {
fun toLibraryItem(): SyncAPI.LibraryItem? {
return SyncAPI.LibraryItem(
name,
@@ -572,6 +589,12 @@ object DataStoreHelper {
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())
}
+ fun getAllBookmarkedData(): List {
+ return getKeys("$currentAccount/$RESULT_WATCH_STATE_DATA")?.mapNotNull {
+ getKey(it)
+ } ?: emptyList()
+ }
+
fun getAllSubscriptions(): List {
return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull {
getKey(it)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 23b1a7ed..e243fe79 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -686,13 +686,32 @@
Qualities
Profile background
UI was unable to be created correctly, this is a MAJOR BUG and should be reported immediately %s
-
-
- tv_no_focus_tag
You have already voted
+
Favorites
%s added to favorites
%s removed from favorites
Add to favorites
Remove from favorites
+
+ Potential Duplicate Found
+ Add
+ Replace
+ Replace All
+ @string/sort_cancel
+
+ 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?
+
+
+ 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?
+
+
+
+ tv_no_focus_tag
From ef36bccc905b584185f9f12fd9cff7a97d22760e Mon Sep 17 00:00:00 2001
From: firelight <147925818+fire-light42@users.noreply.github.com>
Date: Thu, 26 Oct 2023 02:10:08 +0200
Subject: [PATCH 3/4] Delete old MultiAnimeProvider.kt (#717)
---
.../metaproviders/MultiAnimeProvider.kt | 73 -------------------
1 file changed, 73 deletions(-)
delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
deleted file mode 100644
index 8cfe1e9a..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.lagradost.cloudstream3.metaproviders
-
-import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
-import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
-import com.lagradost.cloudstream3.syncproviders.SyncAPI
-import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
-import com.lagradost.cloudstream3.syncproviders.providers.MALApi
-import com.lagradost.cloudstream3.utils.SyncUtil
-
-// wont be implemented
-class MultiAnimeProvider : MainAPI() {
- override var name = "MultiAnime"
- override var lang = "en"
- override val usesWebView = true
- override val supportedTypes = setOf(TvType.Anime)
- private val syncApi: SyncAPI = aniListApi
-
- private val syncUtilType by lazy {
- when (syncApi) {
- is AniListApi -> "anilist"
- is MALApi -> "myanimelist"
- else -> throw ErrorLoadingException("Invalid Api")
- }
- }
-
- private val validApis
- get() =
- synchronized(APIHolder.apis) {
- APIHolder.apis.filter {
- it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
- TvType.Anime
- )
- }
- }
-
-
- private fun filterName(name: String): String {
- return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
- }
-
- override suspend fun search(query: String): List? {
- return syncApi.search(query)?.map {
- AnimeSearchResponse(it.name, it.url, this.name, TvType.Anime, it.posterUrl)
- }
- }
-
- override suspend fun load(url: String): LoadResponse? {
- return syncApi.getResult(url)?.let { res ->
- val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).amap { url ->
- validApis.firstOrNull { api -> url.startsWith(api.mainUrl) }?.load(url)
- }.filterNotNull()
-
- val type =
- if (data.any { it.type == TvType.AnimeMovie }) TvType.AnimeMovie else TvType.Anime
-
- newAnimeLoadResponse(
- res.title ?: throw ErrorLoadingException("No Title found"),
- url,
- type
- ) {
- posterUrl = res.posterUrl
- plot = res.synopsis
- tags = res.genres
- rating = res.publicScore
- addTrailer(res.trailers)
- addAniListId(res.id.toIntOrNull())
- recommendations = res.recommendations
- }
- }
- }
-}
\ No newline at end of file
From 4b93524e5765404dce3ca5c4c2f2cda93c654b24 Mon Sep 17 00:00:00 2001
From: Luna712 <142361265+Luna712@users.noreply.github.com>
Date: Wed, 25 Oct 2023 18:15:50 -0600
Subject: [PATCH 4/4] Convert the rest of SingleSelectionHelper to ViewBinding
(#724)
---
.../utils/SingleSelectionHelper.kt | 51 ++++++++++---------
1 file changed, 28 insertions(+), 23 deletions(-)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
index 5d54ffe5..f34e7238 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
@@ -7,10 +7,7 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.AbsListView
import android.widget.ArrayAdapter
-import android.widget.EditText
-import android.widget.ImageView
import android.widget.LinearLayout
-import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
@@ -20,8 +17,10 @@ import androidx.core.view.marginRight
import androidx.core.view.marginTop
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
+import com.lagradost.cloudstream3.databinding.BottomInputDialogBinding
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
+import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
+import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
@@ -56,14 +55,14 @@ object SingleSelectionHelper {
if (this == null) return
if (isTvSettings()) {
- val builder =
- AlertDialog.Builder(this, R.style.AlertDialogCustom)
- .setView(R.layout.options_popup_tv)
+ val binding = OptionsPopupTvBinding.inflate(layoutInflater)
+ val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom)
+ .setView(binding.root)
+ .create()
- val dialog = builder.create()
dialog.show()
- dialog.findViewById(R.id.listview1)?.let { listView ->
+ binding.listview1.let { listView ->
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
listView.adapter =
ArrayAdapter(this, R.layout.sort_bottom_single_choice_color).apply {
@@ -76,7 +75,7 @@ object SingleSelectionHelper {
}
}
- dialog.findViewById(R.id.imageView)?.apply {
+ binding.imageView.apply {
isGone = poster.isNullOrEmpty()
setImage(poster)
}
@@ -107,12 +106,12 @@ object SingleSelectionHelper {
if (this == null) return
val realShowApply = showApply || isMultiSelect
- val listView = binding.listview1//.findViewById(R.id.listview1)!!
- val textView = binding.text1//.findViewById(R.id.text1)!!
- val applyButton = binding.applyBtt//.findViewById(R.id.apply_btt)
- val cancelButton = binding.cancelBtt//findViewById(R.id.cancel_btt)
+ val listView = binding.listview1
+ val textView = binding.text1
+ val applyButton = binding.applyBtt
+ val cancelButton = binding.cancelBtt
val applyHolder =
- binding.applyBttHolder//.findViewById(R.id.apply_btt_holder)
+ binding.applyBttHolder
applyHolder.isVisible = realShowApply
if (!realShowApply) {
@@ -175,8 +174,8 @@ object SingleSelectionHelper {
}
}
-
private fun Activity?.showInputDialog(
+ binding: BottomInputDialogBinding,
dialog: Dialog,
value: String,
name: String,
@@ -186,11 +185,11 @@ object SingleSelectionHelper {
) {
if (this == null) return
- val inputView = dialog.findViewById(R.id.nginx_text_input)!!
- val textView = dialog.findViewById(R.id.text1)!!
- val applyButton = dialog.findViewById(R.id.apply_btt)!!
- val cancelButton = dialog.findViewById(R.id.cancel_btt)!!
- val applyHolder = dialog.findViewById(R.id.apply_btt_holder)!!
+ val inputView = binding.nginxTextInput
+ val textView = binding.text1
+ val applyButton = binding.applyBtt
+ val cancelButton = binding.cancelBtt
+ val applyHolder = binding.applyBttHolder
applyHolder.isVisible = true
textView.text = name
@@ -352,11 +351,17 @@ object SingleSelectionHelper {
dismissCallback: () -> Unit,
callback: (String) -> Unit,
) {
- val builder = BottomSheetDialog(this) // probably the stuff at the bottom
- builder.setContentView(R.layout.bottom_input_dialog) // input layout
+ val builder = BottomSheetDialog(this)
+
+ val binding: BottomInputDialogBinding = BottomInputDialogBinding.inflate(
+ LayoutInflater.from(this)
+ )
+
+ builder.setContentView(binding.root)
builder.show()
showInputDialog(
+ binding,
builder,
value,
name,