From fd1620f3d7f9d0fca58123a4eefae79a06f985db Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Fri, 13 Oct 2023 22:34:26 +0000 Subject: [PATCH 01/44] Fix unresponsive settings (#688) * Lower targetSdk to get all installed packages * Update sdk version * Let's not be too radical * Many fixes * Revert targetSdk * Make account homepage persistent * Update mobile_navigation.xml * Update SettingsFragment.kt --- .../ui/settings/SettingsFragment.kt | 16 +-- .../main/res/navigation/mobile_navigation.xml | 103 +++++++++--------- 2 files changed, 59 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 4895b0d2..a4d19eba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -187,13 +187,13 @@ class SettingsFragment : Fragment() { } binding?.apply { listOf( - settingsGeneral to R.id.action_navigation_settings_to_navigation_settings_general, - settingsPlayer to R.id.action_navigation_settings_to_navigation_settings_player, - settingsCredits to R.id.action_navigation_settings_to_navigation_settings_account, - settingsUi to R.id.action_navigation_settings_to_navigation_settings_ui, - settingsProviders to R.id.action_navigation_settings_to_navigation_settings_providers, - settingsUpdates to R.id.action_navigation_settings_to_navigation_settings_updates, - settingsExtensions to R.id.action_navigation_settings_to_navigation_settings_extensions, + settingsGeneral to R.id.action_navigation_global_to_navigation_settings_general, + settingsPlayer to R.id.action_navigation_global_to_navigation_settings_player, + settingsCredits to R.id.action_navigation_global_to_navigation_settings_account, + settingsUi to R.id.action_navigation_global_to_navigation_settings_ui, + settingsProviders to R.id.action_navigation_global_to_navigation_settings_providers, + settingsUpdates to R.id.action_navigation_global_to_navigation_settings_updates, + settingsExtensions to R.id.action_navigation_global_to_navigation_settings_extensions, ).forEach { (view, navigationId) -> view.apply { setOnClickListener { @@ -212,4 +212,4 @@ class SettingsFragment : Fragment() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 6d0a94fb..d0df339b 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -331,57 +331,56 @@ app:exitAnim="@anim/exit_anim" app:popEnterAnim="@anim/enter_anim" app:popExitAnim="@anim/exit_anim" - tools:layout="@layout/main_settings"> - - - - - - - - + tools:layout="@layout/main_settings" /> + + + + + + + - \ No newline at end of file + From b7322ffb192d3f500e3d3070854fdcdd2b40f0e0 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:44:51 -0600 Subject: [PATCH 02/44] Add clear search query icons (#687) --- .../cloudstream3/ui/library/LibraryFragment.kt | 9 +++++++++ .../ui/quicksearch/QuickSearchFragment.kt | 13 ++++++++++--- .../cloudstream3/ui/search/SearchFragment.kt | 13 ++++++++++--- app/src/main/res/layout/fragment_library.xml | 2 ++ app/src/main/res/layout/fragment_library_tv.xml | 1 + app/src/main/res/layout/fragment_search.xml | 1 + app/src/main/res/layout/fragment_search_tv.xml | 1 + app/src/main/res/layout/quick_search.xml | 2 ++ 8 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index 85f0aedd..c3986dca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -8,12 +8,14 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS import android.view.ViewGroup.FOCUS_BLOCK_DESCENDANTS import android.view.animation.AlphaAnimation +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast @@ -137,6 +139,13 @@ class LibraryFragment : Fragment() { tag = "tv_no_focus_tag" } + // Set the color for the search exit icon to the correct theme text color + val searchExitIcon = binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) + binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { libraryViewModel.sort(ListSorting.Query, query) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index 53c7c2fa..5b300c06 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.res.Configuration import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -215,10 +216,16 @@ class QuickSearchFragment : Fragment() { binding?.quickSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) //val searchMagIcon = - // binding.quickSearch.findViewById(androidx.appcompat.R.id.search_mag_icon) + // binding?.quickSearch?.findViewById(androidx.appcompat.R.id.search_mag_icon) - //searchMagIcon?.scaleX = 0.65f - //searchMagIcon?.scaleY = 0.65f + // searchMagIcon?.scaleX = 0.65f + // searchMagIcon?.scaleY = 0.65f + + // Set the color for the search exit icon to the correct theme text color + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) binding?.quickSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index ce92d723..65dfd679 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.search import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -231,9 +232,15 @@ class SearchFragment : Fragment() { val searchExitIcon = binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) // val searchMagIcon = - // main_search.findViewById(androidx.appcompat.R.id.search_mag_icon) - //searchMagIcon.scaleX = 0.65f - //searchMagIcon.scaleY = 0.65f + // binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_mag_icon) + // searchMagIcon.scaleX = 0.65f + // searchMagIcon.scaleY = 0.65f + + // Set the color for the search exit icon to the correct theme text color + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) selectedApis = DataStoreHelper.searchPreferenceProviders.toMutableSet() diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 879ddbd9..446c2a81 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -68,6 +68,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:layout_marginEnd="25dp" android:iconifiedByDefault="false" android:imeOptions="actionSearch" @@ -81,6 +82,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_library_tv.xml b/app/src/main/res/layout/fragment_library_tv.xml index ec6565ee..22b9feb1 100644 --- a/app/src/main/res/layout/fragment_library_tv.xml +++ b/app/src/main/res/layout/fragment_library_tv.xml @@ -102,6 +102,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c5a43b6d..7dc2b9d2 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -49,6 +49,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index e34b0ac3..8b352c1f 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -50,6 +50,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/quick_search.xml b/app/src/main/res/layout/quick_search.xml index f34591b0..12d94aaa 100644 --- a/app/src/main/res/layout/quick_search.xml +++ b/app/src/main/res/layout/quick_search.xml @@ -49,6 +49,8 @@ app:queryBackground="@color/transparent" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" + android:paddingStart="-10dp" android:iconifiedByDefault="false" app:queryHint="@string/search_hint" From 7e9d1ded7fb74f97038948e5e81ee10d58356c84 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Sat, 14 Oct 2023 01:54:34 +0300 Subject: [PATCH 03/44] Larger top poster in TV loading layout (#685) --- .../main/res/layout/fragment_result_tv.xml | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index feaf6fbc..b53639ba 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -124,17 +124,16 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:textColor="?attr/textColor" /> - - @@ -155,7 +153,6 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_width="match_parent" android:layout_height="wrap_content"> - + android:orientation="vertical" + android:layout_marginTop="175dp"> + + + + + + + + + + + + + + - - - - - - - - - - - From 3cb2196e62a2cd435d1d76172d8a9113dff5d573 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:02:12 -0600 Subject: [PATCH 04/44] Add favorites (#682) * Add favorites --- .../syncproviders/providers/LocalList.kt | 45 ++++++++++++---- .../ui/result/ResultFragmentPhone.kt | 27 ++++++++++ .../ui/result/ResultFragmentTv.kt | 42 ++++++++++++++- .../ui/result/ResultViewModel2.kt | 47 +++++++++++++++++ .../cloudstream3/utils/DataStoreHelper.kt | 51 +++++++++++++++++++ .../main/res/layout/fragment_result_swipe.xml | 21 +++++++- .../main/res/layout/fragment_result_tv.xml | 15 +++++- app/src/main/res/values/strings.xml | 5 ++ 8 files changed, 238 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index e6ca9711..71bb2633 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -8,7 +8,9 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.Coroutines.ioWork +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData @@ -69,24 +71,47 @@ class LocalList : SyncAPI { }?.distinctBy { it.first } ?: return null val list = ioWork { - watchStatusIds.groupBy { - it.second.stringRes - }.mapValues { group -> + val isTv = isTvSettings() + + val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { + // None is not something to display + it.stringRes to emptyList() + } + mapOf( + R.string.favorites_list_name to emptyList() + ) + if (!isTv) { + mapOf( + R.string.subscription_list_name to emptyList(), + ) + } else { + emptyMap() + } + + val watchStatusMap = watchStatusIds.groupBy { it.second.stringRes }.mapValues { group -> group.value.mapNotNull { getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) } - } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { + } + + val favoritesMap = mapOf(R.string.favorites_list_name to getAllFavorites().mapNotNull { it.toLibraryItem() }) + + // Don't show subscriptions or favorites on TV + val result = if (isTv) { + baseMap + watchStatusMap + favoritesMap + } else { + val subscriptionsMap = mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { + it.toLibraryItem() + }) + + baseMap + watchStatusMap + subscriptionsMap + favoritesMap + } + + result } - val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { - // None is not something to display - it.stringRes to emptyList() - } + mapOf(R.string.subscription_list_name to emptyList()) - return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, + list.map { SyncAPI.LibraryList(txt(it.key), it.value) }, setOf( ListSorting.AlphabeticalA, ListSorting.AlphabeticalZ, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e5f16dd5..a0d82062 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -445,6 +445,20 @@ open class ResultFragmentPhone : FullScreenPlayer() { ?: txt(R.string.no_data).asStringNull(context) ?: "" CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) } + resultFavorite.setOnClickListener { + val isFavorite = + viewModel.toggleFavoriteStatus() ?: return@setOnClickListener + + val message = if (isFavorite) { + R.string.favorite_added + } else { + R.string.favorite_removed + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } mediaRouteButton.apply { val chromecastSupport = api?.hasChromecastSupport == true alpha = if (chromecastSupport) 1f else 0.3f @@ -564,6 +578,19 @@ open class ResultFragmentPhone : FullScreenPlayer() { binding?.resultSubscribe?.setImageResource(drawable) } + observeNullable(viewModel.favoriteStatus) { isFavorite -> + binding?.resultFavorite?.isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable + + val drawable = if (isFavorite) { + R.drawable.ic_baseline_favorite_24 + } else { + R.drawable.ic_baseline_favorite_border_24 + } + + binding?.resultFavorite?.setImageResource(drawable) + } + observe(viewModel.trailers) { trailers -> setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet! } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 5e4869cc..13734b67 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 @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.DecelerateInterpolator +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible @@ -17,6 +18,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.updateHasTrailers +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent @@ -265,6 +267,7 @@ class ResultFragmentTv : Fragment() { resultEpisodesShow.onFocusChangeListener = rightListener resultDescription.onFocusChangeListener = leftListener resultBookmarkButton.onFocusChangeListener = leftListener + resultFavoriteButton.onFocusChangeListener = leftListener resultEpisodesShow.setOnClickListener { // toggle, to make it more touch accessable just in case someone thinks that a // tv layout is better but is using a touch device @@ -283,7 +286,8 @@ class ResultFragmentTv : Fragment() { resultPlaySeries, resultResumeSeries, resultPlayTrailer, - resultBookmarkButton + resultBookmarkButton, + resultFavoriteButton ) for (requestView in views) { if (!requestView.isVisible) continue @@ -424,6 +428,7 @@ class ResultFragmentTv : Fragment() { val aboveCast = listOf( binding?.resultEpisodesShow, binding?.resultBookmarkButton, + binding?.resultFavoriteButton, ).firstOrNull { it?.isVisible == true } @@ -532,6 +537,41 @@ class ResultFragmentTv : Fragment() { } } + observeNullable(viewModel.favoriteStatus) { isFavorite -> + binding?.resultFavoriteButton?.apply { + isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable + + val drawable = if (isFavorite) { + R.drawable.ic_baseline_favorite_24 + } else { + R.drawable.ic_baseline_favorite_border_24 + } + + val text = if (isFavorite) { + R.string.action_remove_from_favorites + } else { + R.string.action_add_to_favorites + } + + setIconResource(drawable) + setText(text) + setOnClickListener { + val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener + + val message = if (isFavorite) { + R.string.favorite_added + } else { + R.string.favorite_removed + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } + } + } + observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 6acf476a..e5ed7b92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -51,11 +51,14 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.UIHelper.navigate @@ -425,6 +428,9 @@ class ResultViewModel2 : ViewModel() { private val _subscribeStatus: MutableLiveData = MutableLiveData(null) val subscribeStatus: LiveData = _subscribeStatus + private val _favoriteStatus: MutableLiveData = MutableLiveData(null) + val favoriteStatus: LiveData = _favoriteStatus + companion object { const val TAG = "RVM2" //private const val EPISODE_RANGE_SIZE = 20 @@ -868,6 +874,40 @@ class ResultViewModel2 : ViewModel() { return !isSubscribed } + /** + * @return true if added to favorites, false if not. Null if not possible to favorite. + **/ + fun toggleFavoriteStatus(): Boolean? { + val isFavorite = _favoriteStatus.value ?: return null + val response = currentResponse ?: return null + + val currentId = response.getId() + + if (isFavorite) { + removeFavoritesData(currentId) + } else { + val current = getFavoritesData(currentId) + + setFavoritesData( + currentId, + DataStoreHelper.FavoritesData( + currentId, + current?.favoritesTime ?: unixTimeMS, + unixTimeMS, + response.name, + response.url, + response.apiName, + response.type, + response.posterUrl, + response.year + ) + ) + } + + _favoriteStatus.postValue(!isFavorite) + return !isFavorite + } + private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1750,6 +1790,12 @@ class ResultViewModel2 : ViewModel() { } } + private fun postFavorites(loadResponse: LoadResponse) { + val id = loadResponse.getId() + val isFavorite = getFavoritesData(id) != null + _favoriteStatus.postValue(isFavorite) + } + private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return @@ -1887,6 +1933,7 @@ class ResultViewModel2 : ViewModel() { currentResponse = loadResponse postPage(loadResponse, apiRepository) postSubscription(loadResponse) + postFavorites(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 952422a4..4b4157d6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -42,6 +42,7 @@ const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" +const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -406,6 +407,33 @@ object DataStoreHelper { } } + data class FavoritesData( + @JsonProperty("id") override var id: Int?, + @JsonProperty("favoritesTime") val favoritesTime: Long, + @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override var type: TvType? = null, + @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("year") val year: Int?, + @JsonProperty("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, + ) : SearchResponse { + fun toLibraryItem(): SyncAPI.LibraryItem? { + return SyncAPI.LibraryItem( + name, + url, + id?.toString() ?: return null, + null, + null, + null, + latestUpdatedTime, + apiName, type, posterUrl, posterHeaders, quality, this.id + ) + } + } + data class ResumeWatchingResult( @JsonProperty("name") override val name: String, @JsonProperty("url") override val url: String, @@ -579,6 +607,29 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) } + fun getAllFavorites(): List { + return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull { + getKey(it) + } ?: emptyList() + } + + fun removeFavoritesData(id: Int?) { + if (id == null) return + AccountManager.localListApi.requireLibraryRefresh = true + removeKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + + fun setFavoritesData(id: Int?, data: FavoritesData) { + if (id == null) return + setKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString(), data) + AccountManager.localListApi.requireLibraryRefresh = true + } + + fun getFavoritesData(id: Int?): FavoritesData? { + if (id == null) return null + return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short diff --git a/app/src/main/res/layout/fragment_result_swipe.xml b/app/src/main/res/layout/fragment_result_swipe.xml index 4e8e3c14..eb2653d0 100644 --- a/app/src/main/res/layout/fragment_result_swipe.xml +++ b/app/src/main/res/layout/fragment_result_swipe.xml @@ -74,7 +74,7 @@ android:nextFocusUp="@id/result_back" android:nextFocusDown="@id/result_description" android:nextFocusLeft="@id/result_add_sync" - android:nextFocusRight="@id/result_share" + android:nextFocusRight="@id/result_favorite" tools:visibility="visible" @@ -89,10 +89,27 @@ android:layout_gravity="end|center_vertical" app:tint="?attr/textColor" /> + + + + tv_no_focus_tag You have already voted + Favorites + %s added to favorites + %s removed from favorites + Add to favorites + Remove from favorites From 8ed7418fe41d314342ae5cf9bb50c78d46b8b7b1 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 14 Oct 2023 10:30:45 -0600 Subject: [PATCH 05/44] Enable sorting by updated date in LocalList --- .../cloudstream3/syncproviders/providers/LocalList.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 71bb2633..0c01c0d5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -80,7 +80,7 @@ class LocalList : SyncAPI { R.string.favorites_list_name to emptyList() ) + if (!isTv) { mapOf( - R.string.subscription_list_name to emptyList(), + R.string.subscription_list_name to emptyList() ) } else { emptyMap() @@ -115,8 +115,8 @@ class LocalList : SyncAPI { setOf( ListSorting.AlphabeticalA, ListSorting.AlphabeticalZ, -// ListSorting.UpdatedNew, -// ListSorting.UpdatedOld, + ListSorting.UpdatedNew, + ListSorting.UpdatedOld, // ListSorting.RatingHigh, // ListSorting.RatingLow, ) From a7a6f2282a539082b9e864ca36848c0a74e47601 Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Wed, 18 Oct 2023 00:43:29 +0530 Subject: [PATCH 06/44] Update build.gradle.kts (#694) --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b0798e44..cea5684e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,7 +232,7 @@ dependencies { // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") - implementation("com.github.Blatzar:NiceHttp:0.4.3") + implementation("com.github.Blatzar:NiceHttp:0.4.4") // http library // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.5.2") // Util to skip the URI file fuckery 🙏 From eb58cb1184389ddc0eff0de339fcea62939877dd Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:41:05 +0530 Subject: [PATCH 07/44] fix crash when navigation graph is null (#706) --- app/build.gradle.kts | 12 ++++++------ .../com/lagradost/cloudstream3/utils/UIHelper.kt | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cea5684e..73c53292 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,7 +51,7 @@ android { } // https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading - compileSdk = 33 // android 14 is fucked + compileSdk = 34 // android 14 is fucked buildToolsVersion = "34.0.0" defaultConfig { @@ -157,7 +157,7 @@ dependencies { implementation("androidx.test.ext:junit-ktx:1.1.5") testImplementation("org.json:json:20230618") - implementation("androidx.core:core-ktx:1.10.1") // need 34 for higher + implementation("androidx.core:core-ktx:1.12.0") // need 34 for higher implementation("androidx.appcompat:appcompat:1.6.1") // need target 32 for 1.5.0 // dont change this to 1.6.0 it looks ugly af @@ -165,10 +165,10 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.4") // need 34 for higher - implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") - implementation("androidx.navigation:navigation-ui-ktx:2.6.0") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") + implementation("androidx.navigation:navigation-ui-ktx:2.7.4") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 9b40e70e..d5357e0c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -178,9 +178,10 @@ object UIHelper { fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) { try { if (this is FragmentActivity) { - (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.navController?.navigate( - navigation, arguments - ) + val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment? + navHostFragment?.navController?.let { + it.navigate(navigation, arguments) + } } } catch (t: Throwable) { logError(t) From 138dea88c4404aafb8026574c89e69c8e7387ca9 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 23 Oct 2023 19:16:48 +0300 Subject: [PATCH 08/44] Poster cropped at 20% from Top (#693) --- .../ui/result/ResultFragmentTv.kt | 6 +- .../utils/PercentageCropImageView.kt | 94 +++++++++++++++++++ .../main/res/layout/fragment_result_tv.xml | 7 +- 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt 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 13734b67..9ccc7c01 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 @@ -247,7 +247,7 @@ class ResultFragmentTv : Fragment() { binding?.apply { //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f - + val leftListener: View.OnFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (!hasFocus) return@OnFocusChangeListener @@ -804,12 +804,14 @@ class ResultFragmentTv : Fragment() { R.drawable.profile_bg_red, R.drawable.profile_bg_teal ).random() + //Change poster crop area to 20% from Top + backgroundPoster.cropYCenterOffsetPct = 0.20F + backgroundPoster.setImage( d.posterBackgroundImage ?: UiImage.Drawable(error), radius = 0, errorImageDrawable = error ) - resultComingSoon.isVisible = d.comingSoon resultDataHolder.isGone = d.comingSoon UIHelper.populateChips(resultTag, d.tags) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt new file mode 100644 index 00000000..1e572fb7 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt @@ -0,0 +1,94 @@ +package com.lagradost.cloudstream3.utils +//Reference: https://stackoverflow.com/a/29055283 +import android.content.Context +import android.graphics.Matrix +import android.graphics.drawable.Drawable +import android.util.AttributeSet + +class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { + private var mCropYCenterOffsetPct: Float? = null + private var mCropXCenterOffsetPct: Float? = null + constructor(context: Context?) : super(context!!) + constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) + constructor( + context: Context?, attrs: AttributeSet?, + defStyle: Int + ) : super(context!!, attrs, defStyle) + + var cropYCenterOffsetPct: Float + get() = mCropYCenterOffsetPct!! + set(cropYCenterOffsetPct) { + require(cropYCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" } + mCropYCenterOffsetPct = cropYCenterOffsetPct + } + var cropXCenterOffsetPct: Float + get() = mCropXCenterOffsetPct!! + set(cropXCenterOffsetPct) { + require(cropXCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" } + mCropXCenterOffsetPct = cropXCenterOffsetPct + } + + private fun myConfigureBounds() { + if (this.scaleType == ScaleType.MATRIX) { + + val d = this.drawable + if (d != null) { + val dWidth = d.intrinsicWidth + val dHeight = d.intrinsicHeight + val m = Matrix() + val vWidth = width - this.paddingLeft - this.paddingRight + val vHeight = height - this.paddingTop - this.paddingBottom + val scale: Float + var dx = 0f + var dy = 0f + if (dWidth * vHeight > vWidth * dHeight) { + val cropXCenterOffsetPct = + if (mCropXCenterOffsetPct != null) mCropXCenterOffsetPct!!.toFloat() else 0.5f + scale = vHeight.toFloat() / dHeight.toFloat() + dx = (vWidth - dWidth * scale) * cropXCenterOffsetPct + } else { + val cropYCenterOffsetPct = + if (mCropYCenterOffsetPct != null) mCropYCenterOffsetPct!!.toFloat() else 0f + scale = vWidth.toFloat() / dWidth.toFloat() + dy = (vHeight - dHeight * scale) * cropYCenterOffsetPct + } + m.setScale(scale, scale) + m.postTranslate((dx + 0.5f).toInt().toFloat(), (dy + 0.5f).toInt().toFloat()) + this.imageMatrix = m + } + } + } + + // These 3 methods call configureBounds in ImageView.java class, which + // adjusts the matrix in a call to center_crop (android's built-in + // scaling and centering crop method). We also want to trigger + // in the same place, but using our own matrix, which is then set + // directly at line 588 of ImageView.java and then copied over + // as the draw matrix at line 942 of ImageView.java + override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean { + val changed = super.setFrame(l, t, r, b) + myConfigureBounds() + return changed + } + + override fun setImageDrawable(d: Drawable?) { + super.setImageDrawable(d) + myConfigureBounds() + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + myConfigureBounds() + } + // In case you can change the ScaleType in code you have to call redraw() + //fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); + //fullsizeImageView.redraw(); + fun redraw() { + val d = this.drawable + if (d != null) { + // Force toggle to recalculate our bounds + setImageDrawable(null) + setImageDrawable(d) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 64550457..75215183 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -130,14 +130,15 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="250dp" android:visibility="visible"> - + android:scaleType="matrix" + tools:src="@drawable/profile_bg_dark_blue" > + Date: Mon, 23 Oct 2023 10:21:32 -0600 Subject: [PATCH 09/44] Library/LocalList: enable subscriptions on emulator layout (#702) --- .../cloudstream3/syncproviders/providers/LocalList.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 0c01c0d5..99723e90 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions @@ -71,14 +71,14 @@ class LocalList : SyncAPI { }?.distinctBy { it.first } ?: return null val list = ioWork { - val isTv = isTvSettings() + val isTrueTv = isTrueTvSettings() val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { // None is not something to display it.stringRes to emptyList() } + mapOf( R.string.favorites_list_name to emptyList() - ) + if (!isTv) { + ) + if (!isTrueTv) { mapOf( R.string.subscription_list_name to emptyList() ) @@ -96,8 +96,8 @@ class LocalList : SyncAPI { it.toLibraryItem() }) - // Don't show subscriptions or favorites on TV - val result = if (isTv) { + // Don't show subscriptions on TV + val result = if (isTrueTv) { baseMap + watchStatusMap + favoritesMap } else { val subscriptionsMap = mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { From 2a4468eb44fe16c90f3312dbefc06d77010d49e8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:33:44 -0600 Subject: [PATCH 10/44] Add more info on homepage to emulator layout (#698) * Add more info on homepage to emulator layout * Support for continue watching and bookmarks It does some manual changes to avoid having to duplicate the entire layout for minor changes --- .../cloudstream3/ExampleInstrumentedTest.kt | 6 ++- .../ui/home/HomeParentItemAdapter.kt | 17 ++++--- .../ui/home/HomeParentItemAdapterPreview.kt | 47 +++++++++++++++++-- .../main/res/layout/fragment_home_head_tv.xml | 24 +++++++++- .../res/layout/homepage_parent_emulator.xml | 35 ++++++++++++++ 5 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/layout/homepage_parent_emulator.xml diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index a84b2457..faacdf50 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.databinding.FragmentSearchBinding import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomepageParentBinding +import com.lagradost.cloudstream3.databinding.HomepageParentEmulatorBinding import com.lagradost.cloudstream3.databinding.HomepageParentTvBinding import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutTvBinding @@ -119,8 +120,9 @@ class ExampleInstrumentedTest { // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) - testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) - testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 163a60a1..443278a9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -15,7 +15,8 @@ import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable class LoadClickCallback( @@ -34,11 +35,13 @@ open class ParentItemAdapter( ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val root = LayoutInflater.from(parent.context).inflate( - if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent, - parent, - false - ) + val layoutResId = when { + isTrueTvSettings() -> R.layout.homepage_parent_tv + parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator + else -> R.layout.homepage_parent + } + + val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false) val binding = HomepageParentBinding.bind(root) @@ -234,7 +237,7 @@ open class ParentItemAdapter( }) //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() - if (!isTvSettings()) { + if (!isTrueTvSettings()) { title.setOnClickListener { moreInfoClickCallback.invoke(expand) } 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 d7956f39..b7e52b88 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 @@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.ui.search.SearchClickCallback +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog @@ -81,6 +82,28 @@ class HomeParentItemAdapterPreview( parent, false ) else FragmentHomeHeadBinding.inflate(inflater, parent, false) + + if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) { + binding.homeBookmarkParentItemMoreInfo.isVisible = true + + val marginInDp = 50 + val density = binding.horizontalScrollChips.context.resources.displayMetrics.density + val marginInPixels = (marginInDp * density).toInt() + + val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams + params.marginEnd = marginInPixels + binding.horizontalScrollChips.layoutParams = params + binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + ContextCompat.getDrawable( + parent.context, + R.drawable.ic_baseline_arrow_forward_24 + ), + null + ) + } + HeaderViewHolder( binding, viewModel, @@ -553,12 +576,19 @@ class HomeParentItemAdapterPreview( resumeHolder.isVisible = resumeWatching.isNotEmpty() resumeAdapter.updateList(resumeWatching) - if (binding is FragmentHomeHeadBinding) { - binding.homeWatchParentItemTitle.setOnClickListener { + if ( + binding is FragmentHomeHeadBinding || + binding is FragmentHomeHeadTvBinding && + binding.root.context.isEmulatorSettings() + ) { + val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle + ?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle + + title?.setOnClickListener { viewModel.popup( HomeViewModel.ExpandableHomepageList( HomePageList( - binding.homeWatchParentItemTitle.text.toString(), + title.text.toString(), resumeWatching, false ), 1, false @@ -576,8 +606,15 @@ class HomeParentItemAdapterPreview( bookmarkHolder.isVisible = visible bookmarkAdapter.updateList(list) - if (binding is FragmentHomeHeadBinding) { - binding.homeBookmarkParentItemTitle.setOnClickListener { + if ( + binding is FragmentHomeHeadBinding || + binding is FragmentHomeHeadTvBinding && + binding.root.context.isEmulatorSettings() + ) { + val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle + ?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle + + title?.setOnClickListener { val items = toggleList.map { it.first }.filter { it.isChecked } if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog val textSum = items diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index 05cb3a41..6db7536f 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -232,7 +232,9 @@ android:layout_marginStart="@dimen/navbar_width" android:layout_marginEnd="0dp" android:padding="12dp" - android:text="@string/continue_watching" /> + android:text="@string/continue_watching" + android:background="?android:attr/selectableItemBackground" + app:drawableTint="?attr/white" /> + + + + + + + + + + + \ No newline at end of file From 48053164dc9737fefeb66a4d6dadc5fcc6410f3a Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 23 Oct 2023 19:38:53 +0300 Subject: [PATCH 11/44] Old APIs focus fixes (#692) * Movie button focus fix in TV layout * Cast item focusable in old API * Media description focus fix in old API * Switch account button focus fix in old APi --- .../com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt | 1 + app/src/main/res/layout/cast_item.xml | 2 +- app/src/main/res/layout/fragment_home_tv.xml | 1 + app/src/main/res/layout/fragment_result_tv.xml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) 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 9ccc7c01..feb8ca04 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 @@ -575,6 +575,7 @@ class ResultFragmentTv : Fragment() { observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success + resultPlaySeries.isVisible = data == null seriesHolder.isVisible = data == null resultEpisodesShow.isVisible = data == null diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml index 368fa770..f164384b 100644 --- a/app/src/main/res/layout/cast_item.xml +++ b/app/src/main/res/layout/cast_item.xml @@ -9,7 +9,7 @@ android:layout_margin="5dp" android:foreground="@drawable/outline_drawable" app:cardBackgroundColor="@color/transparent" - + android:focusable="true" app:cardCornerRadius="@dimen/rounded_image_radius" app:cardElevation="0dp"> diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml index e44457d9..86ef884e 100644 --- a/app/src/main/res/layout/fragment_home_tv.xml +++ b/app/src/main/res/layout/fragment_home_tv.xml @@ -140,6 +140,7 @@ android:layout_gravity="end" android:background="@drawable/player_button_tv_attr_no_bg" android:contentDescription="@string/account" + android:focusable="true" android:nextFocusLeft="@id/home_preview_search_button" android:nextFocusRight="@id/home_switch_account" android:nextFocusDown="@id/home_change_api" diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 75215183..51add4cb 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -419,6 +419,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:fadingEdgeLength="30dp" android:foreground="@drawable/outline_drawable" android:maxLines="7" + android:focusable="true" android:nextFocusUp="@id/result_back" android:nextFocusDown="@id/result_bookmark_button" android:padding="5dp" From 504258bf152d773e3f09e0152778f7168f5648a8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:42:17 -0600 Subject: [PATCH 12/44] Show confirm exit dialog on emulator layout (#704) --- app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 4e0d93c9..a4d1c7b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -662,7 +662,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val isAtHome = navController?.currentDestination?.matchDestination(R.id.navigation_home) == true - if (isAtHome && isTrueTvSettings()) { + if (isAtHome && isTvSettings()) { showConfirmExitDialog() } else { super.onBackPressed() 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 13/44] 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 14/44] 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 15/44] 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 16/44] 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, From 51a877f405ced441914f860770e7366f5c1f16d7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:29:16 -0600 Subject: [PATCH 17/44] Fix some strings (#730) --- .../cloudstream3/ui/library/LibraryFragment.kt | 2 +- app/src/main/res/values-am/strings.xml | 2 -- app/src/main/res/values-ar/strings.xml | 6 ++---- app/src/main/res/values-ars/strings.xml | 3 +-- app/src/main/res/values-bg/strings.xml | 5 ++--- app/src/main/res/values-bn/strings.xml | 2 -- app/src/main/res/values-bp/strings.xml | 6 ++---- app/src/main/res/values-cs/strings.xml | 6 ++---- app/src/main/res/values-de/strings.xml | 6 ++---- app/src/main/res/values-el/strings.xml | 6 ++---- app/src/main/res/values-eo/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 6 ++---- app/src/main/res/values-fr/strings.xml | 6 ++---- app/src/main/res/values-gl/strings.xml | 2 -- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-hr/strings.xml | 6 ++---- app/src/main/res/values-hu/strings.xml | 5 ++--- app/src/main/res/values-in/strings.xml | 6 ++---- app/src/main/res/values-it/strings.xml | 6 ++---- app/src/main/res/values-iw/strings.xml | 6 ++---- app/src/main/res/values-ja/strings.xml | 5 ++--- app/src/main/res/values-kn/strings.xml | 2 -- app/src/main/res/values-ko/strings.xml | 5 ++--- app/src/main/res/values-lt/strings.xml | 3 +-- app/src/main/res/values-lv/strings.xml | 5 ++--- app/src/main/res/values-mk/strings.xml | 5 ++--- app/src/main/res/values-ml/strings.xml | 5 ++--- app/src/main/res/values-ms/strings.xml | 1 - app/src/main/res/values-my/strings.xml | 6 ++---- app/src/main/res/values-nl/strings.xml | 6 ++---- app/src/main/res/values-nn/strings.xml | 5 ++--- app/src/main/res/values-no/strings.xml | 5 ++--- app/src/main/res/values-or/strings.xml | 3 +-- app/src/main/res/values-pl/strings.xml | 6 ++---- app/src/main/res/values-pt/strings.xml | 6 ++---- app/src/main/res/values-qt/strings.xml | 5 ++--- app/src/main/res/values-ro/strings.xml | 6 ++---- app/src/main/res/values-ru/strings.xml | 6 ++---- app/src/main/res/values-sk/strings.xml | 5 ++--- app/src/main/res/values-so/strings.xml | 5 ++--- app/src/main/res/values-sv/strings.xml | 5 ++--- app/src/main/res/values-ta/strings.xml | 2 -- app/src/main/res/values-tl/strings.xml | 5 ++--- app/src/main/res/values-tr/strings.xml | 6 ++---- app/src/main/res/values-uk/strings.xml | 8 +++----- app/src/main/res/values-ur/strings.xml | 6 ++---- app/src/main/res/values-vi/strings.xml | 6 ++---- app/src/main/res/values-zh-rTW/strings.xml | 6 ++---- app/src/main/res/values-zh/strings.xml | 6 ++---- app/src/main/res/values/strings.xml | 13 +++++++------ 50 files changed, 87 insertions(+), 161 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index c3986dca..7cc57f5d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -62,7 +62,7 @@ const val LIBRARY_FOLDER = "library_folder" enum class LibraryOpenerType(@StringRes val stringRes: Int) { - Default(R.string.default_subtitles), // TODO FIX AFTER MERGE + Default(R.string.action_default), Provider(R.string.none), Browser(R.string.browser), Search(R.string.search), diff --git a/app/src/main/res/values-am/strings.xml b/app/src/main/res/values-am/strings.xml index 32f4dcd7..3ea31b54 100644 --- a/app/src/main/res/values-am/strings.xml +++ b/app/src/main/res/values-am/strings.xml @@ -74,12 +74,10 @@ ማውረድ ቀጥል የጽሑፍ ቀለም የተጠናቀቀ - ምንም የፊልም ማስታወቂያ አጫውት የቀጥታ ስርጭት አጫውት ፋይል አጫውት እንደገና በማየት ላይ - ሰርዝ ወደ ኋላ መመለሻ መረጃ ያስቀምጡ diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 8805ec5d..f73081f7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -36,7 +36,6 @@ مكتمل مهمل أخطط لمشاهدته - لا شيء إعادة المشاهدة مشاهدة الفيلم تشغيل بث حي @@ -74,7 +73,6 @@ ازالة إعداد حالة المشاهدة تطبيق - إلغاء نسخ إغلاق مسح @@ -180,6 +178,7 @@ لم يتم العثور على أي حلقات حذف الملف حذف + إلغاء إيقاف مؤقت إستئناف -٣٠ @@ -198,7 +197,7 @@ القصة في قائمة الانتظار الترجمة ليست موجودة - الإفتراضي + الإفتراضي فارغ مستخدم التطبيق @@ -581,7 +580,6 @@ تعذر إنشاء واجهة المستخدم بشكل صحيح ، وهذا خطأ كبير ويجب الإبلاغ عنه على الفور %s حدد الوضع لتصفية تنزيل المكونات الإضافية تعطيل - @string/default_subtitles لا توجد اضافة في المستودع المستودع لم يتم العثور عليه، تحقق من العنوان اوجرب شبكة افتراضية خاصة(vpn) لقد صوتت بالفعل diff --git a/app/src/main/res/values-ars/strings.xml b/app/src/main/res/values-ars/strings.xml index a1042b7e..11dbddc0 100644 --- a/app/src/main/res/values-ars/strings.xml +++ b/app/src/main/res/values-ars/strings.xml @@ -34,7 +34,6 @@ الأنواع توقف التنزيل خطط للمشاهدة - لا يوجد إعادة المشاهدة !تم العثور على تحديث جديد \n%s->%s @@ -125,6 +124,7 @@ موسم تم نسخ الرابط إلى الحافظة مسح + الغي وقف جارٍ تنزيل تحديث التطبيق… إعادة التعيين إلى القيمة العادية @@ -208,7 +208,6 @@ استئناف تحميل معلومات وقفة التحميل - الغي احفظ إعدادات الترجمة لون الخط diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index e9ae65a1..e8c5d0f5 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -41,7 +41,6 @@ Завършено Изпуснат План за гледане - Нито един Повторно гледане Пускане на филм Възпроизвеждане на живо @@ -78,7 +77,6 @@ Премахване Задайте статус на гледане Приложи - Отказ Копирай Затвори Изчисти @@ -187,6 +185,7 @@ Няма намерени епизоди Изтрий файла Изтрий + Отказ Пауза Продължи -30 @@ -205,7 +204,7 @@ Синопсис На опашката Без субтитри - По подразбиране + По подразбиране Безплатно Използвано Приложения diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index c55c8943..b0359c1c 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -43,7 +43,6 @@ শেষ বাদ দেখার ইচ্ছায় - কোন কিছুই না পুনরায় দেখা হচ্ছে টরেন্ট স্ট্রিম করুন উৎসসমূহ @@ -72,7 +71,6 @@ বাদ দিন বুকমার্ক করুন প্রয়োগ করুন - বাদ দিন কপি করুন বন্ধ করুন মুছুন diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 7116a167..1c26c236 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -44,7 +44,6 @@ Completado Deixado Planejando assistir - Nenhum Reassistindo Assistir Filme Transmitir Torrent @@ -81,7 +80,6 @@ Remover Selecionar marcador Aplicar - Cancelar Copiar Fechar Limpar @@ -185,6 +183,7 @@ Nenhum Episódio encontrado Apagar Arquivo Deletar + Cancelar Pausar Retomar -30 @@ -203,7 +202,7 @@ Sinopse Na fila Sem Legendas - Padrão + Padrão Livre Usado App @@ -569,7 +568,6 @@ Wi-Fi Lista de videos da web A interface de usuário não foi gerada corretamente. Isto se trata de um bug importante e deve ser reportado imediatamente %s - Legendas padrão da conta Características da interface de usuário Provedor de teste Layout diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 46bd860d..8bdc1a27 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -40,7 +40,6 @@ Dokončeno Zahozeno Plánuji sledovat - Žádné Opětovné sledování Přehrát film Streamovat torrent @@ -76,7 +75,6 @@ Odebrat Nastavit stav sledování Použít - Zrušit Kopírovat Zavřít Vymazat @@ -176,6 +174,7 @@ Nenalezeny žádné epizody Smazat soubor Smazat + Zrušit Pozastavit Pokračovat -30 @@ -194,7 +193,7 @@ Synopse ve frontě Žádné titulky - Výchozí + Výchozí Volné Použito Aplikace @@ -575,6 +574,5 @@ Výběr režimu pro filtrování stahování doplňků V repozitáři nebyly nalezeny žádné doplňky Repozitář nenalezen, zkontrolujte adresu URL a zkuste použít VPN - @string/default_subtitles Již jste hlasovali diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 85d9ab71..bc3119a3 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -52,7 +52,6 @@ Abgeschlossen Abgebrochen Geplant - Nichts Erneut schauen Film abspielen Livestream abspielen @@ -89,7 +88,6 @@ Entfernen Status setzen Anwenden - Abbrechen Kopieren Schließen Leeren @@ -192,6 +190,7 @@ E Keine Episoden gefunden Löschen + Abbrechen Pause Fortsetzen -30 @@ -210,7 +209,7 @@ Zusammenfassung In Warteschlange eingereiht Keine Untertitel - Standard + Standard Frei Belegt App @@ -552,5 +551,4 @@ Repository nicht gefunden, überprüf die URL und versuch ein VPN Die Benutzeroberfläche konnte nicht korrekt erstellt werden. Dies ist ein SCHWERWIEGENDER FEHLER und sollte sofort gemeldet werden. %s Deaktivieren - @string/default_subtitles diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e88e4fc0..72a7c4c9 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -23,7 +23,6 @@ Ολοκληρώθηκε Διακόπηκε Για παρακολούθηση - Τίποτα Αναπαραγωγή ταινίας Μετάδοση Torrent Πηγές @@ -58,7 +57,6 @@ Αφαίρεση Αναπαραγωγή επεισοδίου Υποβολή - Ακύρωση Ταχύτητα αναπαραγωγής Ρυθμίσεις υπότιτλων Χρώμα κειμένου @@ -155,6 +153,7 @@ Δεν βρέθηκαν επεισόδια Διαγραφή αρχείου Διαγραφή + Ακύρωση Παύση Συνέχιση Αυτό θα διαγράψει μόνιμα το %s @@ -169,7 +168,7 @@ Περίληψη προστέθηκε στην ουρά Δεν υπάρχουν διαθέσιμοι υπότιτλοι - Προεπιλεγμένοι υπότιτλοι + Προεπιλεγμένοι υπότιτλοι Ελεύθερος Σε χρήση Εφαρμογή @@ -547,6 +546,5 @@ Το UI δεν ήταν σε θέση να δημιουργηθεί σωστά, είναι ένα σφάλμα και θα πρέπει να αναφερθεί αμέσως %s Επιλέξτε κατάσταση για φιλτράρισμα επεκτάσεων για λήψη Απενεργοποιημένο - @string/default_subtitles Τέλος diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 49f025d0..6dcf12c3 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -22,7 +22,6 @@ Priskribo Versio Stato - Nuligi Forviŝi Jes Ne diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 51063b4d..69086840 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -45,7 +45,7 @@ Cargar desde Internet Reproducir automáticamente episodio siguiente Configuración de subtítulos de Chromecast - Predeterminado + Predeterminado Contorno Sin Subtítulos Elevado @@ -138,7 +138,6 @@ Completado Descartado Planeando ver - Ninguno Volviendo a mirar Reproducir película Reproducir Trailer @@ -167,7 +166,6 @@ Marcadores Remover Seleccionar estado de visualización - Cancelar Copiar Cerrar Limpiar @@ -256,6 +254,7 @@ T Borrar Archivo Borrar + Cancelar Error inesperado del reproductor Episodio en Chromecast Reproducir en la aplicación @@ -549,7 +548,6 @@ La interfaz de usuario no se ha podido crear correctamente, se trata de un GRAN BUG y debe ser reportado inmediatamente %s Seleccionar modo para filtrar los plugins descargados Deshabilitar - @string/default_subtitles No se encontraron complementos en el repositorio Repositorio no encontrado, comprueba la URL y prueba la VPN Ya has votado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 53442e84..2530a068 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -22,7 +22,6 @@ Terminé Abandonné À regarder - Aucun Lire Streamer le Torrent Sources @@ -60,7 +59,6 @@ Marque-pages Supprimer Appliquer - Annuler Vitesse de lecture Aperçu de l\'arrière-plan Donner une benene aux devs @@ -80,6 +78,7 @@ E Supprimer le Fichier Supprimer + Annuler Pause Reprendre Cela va supprimer définitivement %s @@ -94,7 +93,7 @@ Synopsis Liste d\'attente Pas de sous-titres - Défault + Défault Libre Utilisé Application @@ -553,5 +552,4 @@ L\'interface utilisateur n\'a pas pu être créée correctement. Il s\'agit d\'un bogue majeur qui doit être signalé immédiatement %s Sélectionnez le mode pour filtrer le téléchargement des plugins Fond de profil - @string/default_subtitles diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index ad7916be..92754082 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -29,13 +29,11 @@ Completado Descartado Planeando ver - Ningún Remirando Marcadores Borrar Seleccionar estado de visualización Aplicar - Cancelar Copiar Cerrar Limpar diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 31e0e28c..5053544d 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -49,7 +49,6 @@ बुकमार्क्स हटाएँ लागू करें - रद्द करें प्लेयर स्पीड प्रोवाइडरों का उपयोग कर खोजें प्रकार का उपयोग करके खोजें @@ -91,6 +90,7 @@ क्षमा करें, एप्प क्रैश हो गया है । निर्माताओं को एक अनाम बग रिपोर्ट भेजी जाएगी फ़ाइल डिलीट करें डिलीट + रद्द करें रोकें फिर से चलाएं इससे %s स्थायी रूप से हट जाएगा diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 84915a38..8da04be6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -54,7 +54,6 @@ Dovršeno Ispušteno Planiram pogledati - Ništa Ponovno gledam Pokreni Film Pokreni LiveStream @@ -92,7 +91,6 @@ Ukloni Postavi status gledanja Primijeni - Poništi Kopiraj Zatvori Očisti @@ -200,6 +198,7 @@ Nisu pronađene epizode Izbriši datoteku Izbriši + Poništi Pauziraj Nastavi -30 @@ -218,7 +217,7 @@ Sinopsis u redu čekanja Bez titlova - Zadano + Zadano Slobodno Iskorišteno Aplikacija @@ -567,7 +566,6 @@ Nije bilo moguće ispravno izraditi korisničko sučelje. Ovo je ZNAČAJNA GREŠKA i treba se odmah prijaviti %s Odaberi modus za filtriranje preuzimanja dodataka Onemogući - @string/default_subtitles U repozitoriju nisu pronađeni dodaci Repozitorij nije pronađen, provjerite URL i pokušajte koristiti VPN Ovdje možete promijeniti način na koji su izvori poredani. Ako video ima viši prioritet, pojavit će se više u odabiru izvora. Zbroj prioriteta izvora i prioriteta kvalitete je video prioritet. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 677beaf8..4cd322a9 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -20,6 +20,7 @@ Letöltés Keresés Törlés + Mégse Szüneteltetés sorba állítva Igazítás @@ -61,7 +62,6 @@ Nézés Befejezve Később megnézés - Nincs Újranézés Film lejátszása Előzetes lejátszása @@ -90,7 +90,6 @@ Eltávolítás Megtekintés állapotának beállítása Alkalmazás - Mégse Másolás Bezárás Törlés @@ -162,7 +161,7 @@ Értékelés Rajzfilmek Élőadások - Alapértelmezett + Alapértelmezett Filmek TV sorozat Anime diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d514bcc4..a6c5813d 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -39,7 +39,6 @@ Selesai Dihentikan Rencana untuk Menonton - Tidak Ada Menonton Ulang Putar Movie Streaming Torrent @@ -75,7 +74,6 @@ Hapus Atur status tontonan Terapkan - Batalkan Salin Tutup Bersihkan @@ -174,6 +172,7 @@ Episode Tidak Ditemukan Hapus File Hapus + Batalkan Jeda Lanjutkan -30 @@ -192,7 +191,7 @@ Sinopsis antri Tidak Ada Subtitle - Default + Default Tersedia Terpakai Aplikasi @@ -575,5 +574,4 @@ Tidak ada plugin yang ditemukan di repositori Repositori tidak ditemukan, periksa URL dan coba VPN Kamu sudah voting - @string/default_subtitles diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0c34e89a..933ac77f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -44,7 +44,6 @@ Completato Abbandonato Da guardare - Nessuno Riguardando Riproduci film Riproduci Livestream @@ -81,7 +80,6 @@ Rimuovi Imposta stato riproduzione Applica - Cancella Copia Chiudi Cancella @@ -190,6 +188,7 @@ Nessun episodio trovato Elimina file Elimina + Cancella Pausa Riprendi -30 @@ -208,7 +207,7 @@ Sinossi In coda Nessun sottotiolo - Default + Default Libero Usato App @@ -572,7 +571,6 @@ Repository non trovato, controlla l\'URL e prova la VPN Non è stato possibile creare correttamente l\'interfaccia utente, questo è un GRANDE BUG e dovrebbe essere segnalato immediatamente %s Seleziona la modalità per filtrare il download dei plugin - @string/default_subtitles Disabilita Hai già votato diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index d6d5b7f6..55666df5 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -66,7 +66,6 @@ הסר הגדר מצב צפייה ליישם - בטל העתק לסגור נקה @@ -78,7 +77,6 @@ צופה כתוביות בהמתנה - ללא להוריד מדובב יותר מידע @@ -146,6 +144,7 @@ לא נמצאו פרקים מחק קובץ מחק + בטל השהה המשך -30 @@ -159,7 +158,7 @@ דירוג שנה ללא כתוביות - ברירת מחדל + ברירת מחדל חינם משומש סדרת טלוויזיה @@ -519,7 +518,6 @@ עריכה Wi-Fi רקע הפרופיל - @string/default_subtitles רשומה עזרה התחלה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7f10aad4..16341b60 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -37,7 +37,6 @@ アジアドラマ ライブ配信 NSFW - キャンセル アニメ ロック ソース @@ -71,7 +70,6 @@ ソース 履歴 ポスター - なし コピー 閉じる 保存 @@ -135,6 +133,7 @@ 一時停止 再生エピソード 削除 + キャンセル 開始 状態 @@ -155,7 +154,7 @@ バージョン 視聴率 %s 視聴率 - デフォルト + デフォルト ダウンロード失敗 ダウンロード開始 ダウンロード完了 diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 399aafb1..07ff89e4 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -32,7 +32,6 @@ ಮಾಹಿತಿ ಸೆಟ್ ವಾಚ್ ಸ್ಟೇಟಸ್ ಅನ್ವಯಿಸು - ರದ್ದುಮಾಡು ಸಬ್ ಟೈಟಲ್ಸ್ ಎಲೆವಷನ್ ಫಾಂಟ್ ಸೈಜ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಭಾಷೆ @@ -108,7 +107,6 @@ ಪ್ರಕಾರಗಳು ಬ್ರೌಸರ್ ತೆರೆಯಿರಿ ಆನ್-ಹೋಲ್ಡ್ - ನನ್ ಸಂಪರ್ಕವನ್ನು ಮರುಪ್ರಯತ್ನಿಸಿ… ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ ಡೌನ್‌ಲೋಡ್ ವಿಫಲವಾಗಿದೆ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 74c05d07..ef2f6cc5 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -35,7 +35,6 @@ 시청 완료 포기 시청 예정 - 없음 다시보기 영화 재생 예고편 재생 @@ -95,7 +94,6 @@ 백업 더빙 자막 - 취소 북마크 필터 북마크 제거 @@ -177,7 +175,7 @@ 개요 대기중 자막 없음 - 기본 + 기본 남음 사용됨 @@ -352,6 +350,7 @@ 아시아 드라마 시즌 삭제 + 취소 %s %d%s 파일 삭제 일시정지 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index a702a62f..db5ac011 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -65,6 +65,7 @@ Dokumentika Transliuoti Torrentą Ištrinti + Atšaukti Pradėti Prideda greičio pasirinkti grotuve Filmukas @@ -95,7 +96,6 @@ Teksto spalva Užbaigta Naudoti sistemos ryškumą programos grotuve vietoj tamsumo - Tuščia Nepavyko atstatyti duomenis iš failo %s Paleisti anonsa Paleisti gyva transliacija @@ -107,7 +107,6 @@ Peržiūrima Atidaryti Naršyklėje Naudoti sistemos ryškumą - Atšaukti Nėra duomenų Šriftas Perdaryti nustatymo procesą diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index ddd39942..14e5b600 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -31,7 +31,6 @@ Pabeigts Atmests Plāno skatīties - Neviena Atkārtoti skatities Palaist Filmu Palaist Trelleri @@ -67,7 +66,6 @@ Noņemt Ieliec skatīšanās statusu Izmantot - Atcelt Kopēt Saglabāt Atskaņošanas ātrums @@ -192,6 +190,7 @@ Epizodes netika atrastas Dzēsti faili Dzēst + Atcelt Pauzēt Sākt Neizdevās @@ -483,7 +482,7 @@ Iet Bezmaksas Ieslēgt elementus uz plakātiem - Parastais + Parastais Nav atjauninājumi atrasti Izdzēst video un bildes atkritne Izvēlētā skatīšanās kvalitāte (Mobilie Dati) diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 66a6b9ba..7badfd18 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -25,7 +25,6 @@ Завршени Отфрлени Планирани за гледање - Ништо одбрано Повторно гледање Пушти филм Стримај торент @@ -58,7 +57,6 @@ Обележувачи Отстрани Активирај - Откажи Брзина на плеер Поставки за преводи Боја на текстот @@ -133,6 +131,7 @@ Е Избриши датотека Избриши + Откажи Паузирај Продолжи Ова трајно ќе го избрише %s @@ -147,7 +146,7 @@ Крат во редица Нема преводи - Стандардно + Стандардно Слободен простор Искористен простор Апликациски простор diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 3d6240f9..cdc50cea 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -54,7 +54,6 @@ ബുക്മാർക് നീക്കം ചെയ്യുക പ്രയോഗിക്കുക - റദ്ദാക്കുക പ്ലേയർ വേഗത + Default --> ഒഴിവ് ഉപയോഗത്തിൽ ആപ്പ് @@ -197,7 +197,6 @@ ദാതാവിനെ മാറ്റുക ലോഡിംഗ്… ബ്രൗസർ - ഒന്നുമില്ല വീണ്ടും കാണുക സ്ട്രീം diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 222bef61..e579381e 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -49,7 +49,6 @@ Buka dengan Padam Fail Buka Dalam Pelayar - Batal Tiada Data Info Simpan diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 0cb44373..e0346f19 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -32,7 +32,6 @@ ကြည့်နေသည် ကြည့်ပြီး ကြည့်ခြင်းရပ်ထားသော - ဘာမျှ လင့်များချိတ်ဆက်ရာတွင်အချို့အယွင်း ဖုန်း သိုလှောင်ရုံ စာမှတ်များ စစ်ထုတ်မှု @@ -127,8 +126,7 @@ အကျဥ်းချုပ် နောက်အစီအစဥ် စာတန်းထိုးမထည့် - ပုံသေ - @string/default_subtitles + ပုံသေ ကျန်ရှိသော အက်ပ် ရုပ်ရှင်များ @@ -253,7 +251,6 @@ စာတန်းထိုး ဘာသာစကား ဒီမှာနေရာချခြင်းဖြင့်ဖောင့်များကိုသွင်းပါ %s အတည်ပြု - ပယ်ဖျက်ရန် စာတန်းထိုး ပြုပြင်ခြင်း စာသား အရောင် အနားကွပ် အရောင် @@ -301,6 +298,7 @@ အပိုင်းများမတွေ့ပါ ဖိုင်ကိုဖျက်ရန် ဖျက်ရန် + ပယ်ဖျက်ရန် ရပ်ရန် စရန် မအောင်မြင်ပါ diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d19726fd..d73d77a0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -44,7 +44,6 @@ Voltooid Dropped Plan om te kijken - Geen Opnieuw kijken Film afspelen Livestream afspelen @@ -82,7 +81,6 @@ Verwijder Zet kijkstatus Toepassen - annuleer Kopiëren Sluit Wissen @@ -186,6 +184,7 @@ Geen afleveringen gevonden Verwijder bestand Verwijder + annuleer Pauze Hervatten -30 @@ -204,7 +203,7 @@ Korte inhoud wachtrij Geen ondertiteling - Standaard + Standaard Vrij Gebruikt App @@ -573,6 +572,5 @@ Selecteer een modus om het downloaden van plug-ins te filteren Uitzetten De gebruikersinterface kon niet correct worden gemaakt, dit is een ERNSTIG PROBLEEM en moet onmiddellijk gerapporteerd worden %s - @string/default_subtitles Je hebt al gestemd diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 592ff22c..c70ebd4b 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -63,7 +63,6 @@ Fjern Sett visingstatus Bruk - Avbryt Kopier Tøm Lagre @@ -113,6 +112,7 @@ Ingen episodar blei funnen Slett fil Slett + Avbryt Pause Gjenoppta -30 @@ -129,7 +129,7 @@ Om i kø Ingen undertekstar - Standard + Standard Brukt Program Filmar @@ -166,7 +166,6 @@ Gå tilbake Sjangrar Dele - Ingen Ser om igjen Oppdatering starta Fortsett nedlasting diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index dac15d61..9845120b 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -33,7 +33,6 @@ Fullført Falt Planlegg å se - Ingen Spill filmer Strøm Torrent Kilder @@ -67,7 +66,6 @@ Bokmerker Ta bort Søke om - Avbryt Spillerhastighet Innstillinger for teksting Tekstfarge @@ -141,6 +139,7 @@ E Slett fil Slett + Avbryt Stopp Gjenoppta Dette vil slette %s @@ -155,7 +154,7 @@ Om I kø Ingen undertekster - Misligholde + Misligholde Tilgjengelig Brukt applikasjon diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 63dba53d..22f7d1ca 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -19,7 +19,6 @@ ଵେଗ (%.2fଗୁଣ) ତ୍ୟାଗିଛନ୍ତି ଦେଖିବା ପାଇଁ ଇଚ୍ଛୁକ - କିଛି ନାହିଁ ଅଧିକ ସୂଚନା ପାତ୍ର: %s ପୋଷ୍ଟର୍ @@ -42,7 +41,7 @@ ଅଧ୍ୟାୟର ପୋଷ୍ଟର୍ ମୁଖ୍ୟ ପୋଷ୍ଟର୍ - ଡିଫଲ୍ଟ + ଡିଫଲ୍ଟ ଭାଷା ନାହିଁ ଵର୍ଣ୍ଣନା diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a170d610..95a3e1c6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -35,7 +35,6 @@ Zakończone Porzucone Planowane - Brak Ponowne oglądanie Odtwórz film Odtwórz transmisję na żywo @@ -72,7 +71,6 @@ Usuń Ustaw status oglądania Zastosuj - Anuluj Kopiuj Zamknij Wyczyść @@ -181,6 +179,7 @@ Nie znaleziono odcinków Usuń plik Usuń + Anuluj Wstrzymaj Odtwórz -30 @@ -199,7 +198,7 @@ Streszczenie W kolejce Brak napisów - Domyślne + Domyślne Wolne W użyciu Aplikacja @@ -552,7 +551,6 @@ Nie można było poprawnie utworzyć interfejsu użytkownika, jest to POWAŻNY BŁĄD i należy go natychmiast zgłosić %s Wybierz tryb filtrowania pobieranych rozszerzeń Wyłączać - @string/default_subtitles Nie znaleziono żadnych wtyczek w repozytorium Już oddano głos Nie znaleziono tego repozytorium, sprawdź adres URL lub spróbuj połączyć się przez VPN diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 908ddb0d..c73fb996 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -42,7 +42,6 @@ Concluído Desistido Pretendo assistir - Nenhum Reassistindo Reproduzir filme Reproduzir transmissão ao vivo @@ -78,7 +77,6 @@ Marcadores Remover Aplicar - Cancelar Copiar Fechar Limpar @@ -182,6 +180,7 @@ Nenhum Episódio encontrado Eliminar Ficheiro Eliminar + Cancelar Pôr em Pausa Retomar Isto apagará %s permanentemente @@ -198,7 +197,7 @@ Sinopse Na fila Sem Legendas - Padrão + Padrão Livre Usado App @@ -548,7 +547,6 @@ \nNOTA: Se a soma for 10 ou mais, o leitor saltará automaticamente o carregamento quando essa ligação for carregada! Selecionar o modo para filtrar a transferência de plug-ins Não foi possível criar corretamente a interface do utilizador, trata-se de um GRANDE BUG e deve ser comunicado imediatamente %s - \@ string/legendas_padrão Desativar Não foram encontrados plugins no repositório Repositório não encontrado, verifique o URL e tente a VPN diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index 9c68c008..e1191d2b 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -23,7 +23,6 @@ ahhahooo ooooo haa aaahhuoh - aaaghh aaaaa aaaghh aaaghhaaahhu aaaaa oha aauuh @@ -51,7 +50,6 @@ oouuh ooh aauuh ooh oouuhaooo-ahah - oooohh oouuh aauuh oha ouuhhhooooooh ooo-ahah ohaouuhhh @@ -122,6 +120,7 @@ A ooha ohahaaahoooa ahahooo oooooah + oooohh aahhaaaaaahooo aauuh aaahhuaooo-ahahoooohh aoouuhoohoohooo-ahah %s aaaghhaaaaa aauuhaauuh @@ -133,7 +132,7 @@ aauugghh ohaaauugghh haaouuhhh ahaaaaaaaaaahaaaauugghh - ahooooh + ahooooh ooo-ahahaaaaa ahhahhh aauugghh diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index b6971c37..75388b23 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -43,7 +43,6 @@ Finalizat Renunțat Planificare pentru a urmări - Anuleaza Reurmat Urmărește Stream Torrent @@ -79,7 +78,6 @@ Eliminează Adaugă Aplică - Anulează Copiază Închide Elimină @@ -181,6 +179,7 @@ Nu s-au găsit episoade Ștergeți fișierul Ștergeți + Anulează Pauză Continuă -30 @@ -199,7 +198,7 @@ Rezumat În coada de așteptare Nu există subtitrare - Implicit + Implicit Liber Folosit Aplicație @@ -569,6 +568,5 @@ Utilizați UI nu a putut fi creată corect, acesta este un BUG MAJOR și trebuie raportat imediat %s Selectați modul de filtrare a descărcării plugin-urilor - @string/default_subtitles Ați votat deja diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e7b85b3d..64c3146e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -12,6 +12,7 @@ Скачать неудачный Подогнать Удалить + Отмена Все Пауза Актёрский состав: %s @@ -58,7 +59,6 @@ Завершено Брошенный План посмотреть - Нет Пересмотрю Смотреть фильм Смотреть трейлер @@ -82,7 +82,6 @@ Фильтр закладки Закладки Применить - Отмена Копия Закрыть Очистить @@ -186,7 +185,7 @@ Рейтинг Продолжительность Нет субтитров - По умолчанию + По умолчанию Приложение Аниме Торренты @@ -535,7 +534,6 @@ Изменить Интернет Задний фон профиля - \@строка/обычные_субтитры Помощь Профиль %d Выберите режим фильтера плагинов для загрузки diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e0cc27d0..4f17971e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -33,7 +33,6 @@ Hľadať… Sťahovanie Žiadne dáta - Zrušiť Kopírovať Zavrieť Uložiť @@ -60,7 +59,6 @@ Sťahovanie zrušené Dab Zmazať súbor - Žiadny Tit Opätovné sledovanie Prehrať súbor @@ -265,7 +263,7 @@ Preferované médiá URL servera NGINX %d %s - Predvolené + Predvolené Pridať sledovanie Žiadna sezóna Epizóda @@ -293,6 +291,7 @@ Rok Prispôsobiť obrazovke Zmazať + Zrušiť Využité Štítok kvality Prehrávač skrytý - dĺžka pretočenia diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index db82d9fa..c3d107f5 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -10,6 +10,7 @@ Dejintii ma guulaysan Raadi Tirtir + Jooji Haki Horran Le-ekaysii shaashadda @@ -46,7 +47,6 @@ Dhamaystirmay Soo dhacay Ku talo jira - Midna Daaro filinka Daar goos-gooska Midabka daaqadda @@ -56,7 +56,6 @@ Daalaco toorentiga Qrl-hoosaadka Dib u bilow dejinta - Jooji Midabka dambeedka Xigashooyinka Dib u xidhiidhinaya… @@ -202,7 +201,7 @@ Muddada La isticmaalay Qrl-hoosaad ma leh - Sidiisaa + Sidiisaa Bilaash Appka Kartoob diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 397faa48..806a1ca3 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -26,7 +26,6 @@ Avslutad Dropped Plannerad - Ingen Spela Upp Strömma Torrent Källor @@ -53,7 +52,6 @@ Bokmärken Ta bort Tillämpa - Avbryt Spelarhastighet Undertextinställningar Textfärg @@ -125,6 +123,7 @@ A Ta bort nerladdad fil Ta bort + Avbryt %s kommer att raderas permanent \nÄr du helt säker\? Pågående @@ -137,7 +136,7 @@ Sammanfattning på kö Inga undertexter - Standard + Standard Tillgängligt Använtt App diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 9c9a335b..ca4a1377 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -38,7 +38,6 @@ மேலும் தகவல்கள் மறை நீக்கு - ரத்து செய்க நீக்கு சேமிக்கவும் உரை வண்ணம் @@ -63,7 +62,6 @@ நடிகர்கள்: %s பின் செல் அமைப்புகள் - ஏதும் இல்லை ஏற்றுகிறது… கைவிடப்பட்டது பதிவிறக்கம் முடிந்தது diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 95d38478..03138259 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -36,7 +36,6 @@ Tapos nang panoorin Ayaw nang panoorin Balak panoorin - None I-play ang Movie Stream Torrent Sources @@ -70,7 +69,6 @@ Bookmark Tanggalin Kumpirmahin - Kanselahin Bilis ng Playback Subtitle Setting Kulay ng Teksto @@ -144,6 +142,7 @@ E Burahin ang file Tanggalin + Kanselahin I-pause I-resume This will permanently delete %s @@ -158,7 +157,7 @@ Sinopsis nakapila walang subtitles - Default + Default Bakante Gamit App diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e0d7e4cd..c1e06a7b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -57,7 +57,6 @@ Tamamlandı Bırakıldı Planlandı - Hiçbiri Yeniden izleniyor Filmi oynat Canlı yayını oynat @@ -96,7 +95,6 @@ Kaldır İzleme durumunu ayarla Uygula - İptal et Kopyala Kapat Temizle @@ -205,7 +203,7 @@ Bölüm bulunamadı Dosyayı sil Sil - @string/sort_cancel + İptal et Durdur Sürdür -30 @@ -224,7 +222,7 @@ Özet Sıraya alındı Alt yazı yok - Varsayılan + Varsayılan Boş Kullanılan Uygulama diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c069cae0..57b128de 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -112,9 +112,7 @@ Переглянути файл Детальніше Фільтр закладок - Нічого - Скасувати - Очистити + Очистити Налаштування субтитрів Колір фону Висота субтитрів @@ -174,6 +172,7 @@ Е Видалити файл Видалити + Скасувати Відновити -30 Це назавжди видалить %s @@ -186,7 +185,7 @@ Тривалість у черзі Без субтитрів - За замовчуванням + За замовчуванням Вільно Зайнято Застосунок @@ -549,7 +548,6 @@ Не вдалося створити UI коректно, це ВАЖЛИВА ПОМИЛКА, про яку слід негайно повідомити %s Виберіть режим для фільтрації завантаження плагінів Вимкнути - @string/default_subtitles Репозиторій не знайдено, перевірте URL-адресу та спробуйте VPN Не знайдено жодних плагінів у репозиторії Ви вже проголосували diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index b9585d07..b437e2d0 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -34,7 +34,6 @@ معطل چھوڑ دیا گیا دیکھنے کا منصوبہ - کوئی نہیں دوبارہ دیکھنا مووی لگائے ٹریلر چلائیں @@ -67,7 +66,6 @@ ریمو واچ اسٹیٹس کو سیٹ کریں لاگو کریں - منسوخ کریں کاپی بند کریں صاف کریں @@ -193,6 +191,7 @@ کوئی اقساط نہیں ملی فائل کو ڈیلیٹ کریں مٹا دیں + منسوخ کریں توقف از سر نو شروع کریں -30 @@ -211,7 +210,7 @@ خلاصہ قطار میں کوئی سب ٹائٹلز نہیں - ڈیفالٹ + ڈیفالٹ مفت استعمال شُدہ کارٹون @@ -535,7 +534,6 @@ ترتیب دیں وائی فائی پروفائل پس منظر - @string/default_subtitles مدد پروفائل %d پلگ انز کو ڈاؤن لوڈ کرنے کے لئے موڈ منتخب کریں diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 217d2791..8186d6ed 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -45,7 +45,6 @@ Đã xem Bỏ qua Xem sau - Mặc định Xem lại Xem Ngay Phát trực tiếp @@ -83,7 +82,6 @@ Xóa Đặt trạng thái xem Áp dụng - Hủy bỏ Sao lưu Đóng Huỷ bỏ @@ -190,6 +188,7 @@ Không có tập nào Xóa Tệp Xóa + Hủy bỏ Tạm Dừng Tiếp Tục -30 @@ -208,7 +207,7 @@ Thông tin Hàng chờ Không có phụ đề - Mặc Định + Mặc Định Còn trống Đã sử dụng App @@ -567,5 +566,4 @@ Không tìm thấy plugin Không thể khởi tạo UI, đây là một LỖI LỚN và cần được báo cáo ngay lập tức tới %s Chọn chế độ để lọc plugin tải xuống - @string/default_subtitles diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 1fd01d8a..52897633 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -57,7 +57,6 @@ 觀看完畢 放棄觀看 計畫觀看 - 重新觀看 播放電影 播放直播 @@ -96,7 +95,6 @@ 移除 設定觀看狀態 套用 - 取消 複製 關閉 清除 @@ -205,7 +203,7 @@ 未找到劇集 刪除文件 刪除 - @string/sort_cancel + 取消 暫停 繼續 -30 @@ -224,7 +222,7 @@ 簡介 已加入佇列 無字幕 - 預設 + 預設 空閒 已使用 應用程式 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 033a5f50..682bcab7 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -57,7 +57,6 @@ 观看完毕 放弃观看 计划观看 - 重新观看 播放电影 播放直播 @@ -96,7 +95,6 @@ 移除 设置观看状态 应用 - 取消 复制 关闭 清除 @@ -206,7 +204,7 @@ 未找到剧集 删除文件 删除 - @string/sort_cancel + 取消 暂停 继续 -30 @@ -225,7 +223,7 @@ 简介 已加入队列 无字幕 - 默认 + 默认 空闲 已使用 应用 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e243fe79..f0e92d36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -123,7 +123,7 @@ Completed Dropped Plan to Watch - None + @string/none Rewatching Play Movie Play Trailer @@ -164,7 +164,7 @@ Remove Set watch status Apply - Cancel + @string/cancel Copy Close Clear @@ -287,7 +287,7 @@ No Episodes found Delete File Delete - @string/sort_cancel + Cancel Pause Start Failed @@ -307,8 +307,9 @@ Synopsis queued No Subtitles - Default - @string/default_subtitles + Default + @string/action_default + @string/action_default Free Used App @@ -698,7 +699,7 @@ Add Replace Replace All - @string/sort_cancel + @string/cancel It appears that a potentially duplicate item already exists in your library: \'%1$s.\' From f0ebfa47c8ccffd9ae9795d725358f4df54ec259 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:40:20 -0600 Subject: [PATCH 18/44] Bump material to 1.10.0 (#728) --- app/build.gradle.kts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 73c53292..efd98724 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,14 +50,15 @@ android { } } - // https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading - compileSdk = 34 // android 14 is fucked + compileSdk = 34 buildToolsVersion = "34.0.0" defaultConfig { applicationId = "com.lagradost.cloudstream3" minSdk = 21 - targetSdk = 33 + + // https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading + targetSdk = 33 // android 14 is fucked versionCode = 62 versionName = "4.2.1" @@ -157,14 +158,12 @@ dependencies { implementation("androidx.test.ext:junit-ktx:1.1.5") testImplementation("org.json:json:20230618") - implementation("androidx.core:core-ktx:1.12.0") // need 34 for higher - implementation("androidx.appcompat:appcompat:1.6.1") // need target 32 for 1.5.0 + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") - // dont change this to 1.6.0 it looks ugly af - implementation("com.google.android.material:material:1.5.0") + implementation("com.google.android.material:material:1.10.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - // need 34 for higher implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") implementation("androidx.navigation:navigation-ui-ktx:2.7.4") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") From d542febcda773e8e5fac34c6acabb39a005596cc Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:41:19 -0600 Subject: [PATCH 19/44] Reload library when deleting bookmarks (#725) --- .../com/lagradost/cloudstream3/ui/home/HomeViewModel.kt | 6 ------ .../com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index ad75aa9d..f471fefd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds @@ -102,11 +101,6 @@ class HomeViewModel : ViewModel() { loadStoredData() } - fun deleteBookmarks() { - deleteAllBookmarkedData() - loadStoredData() - } - var repo: APIRepository? = null private val _apiName = MutableLiveData() 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 78f801b6..444c2ab2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -483,15 +483,9 @@ object DataStoreHelper { removeKeys(folder) } - fun deleteAllBookmarkedData() { - val folder1 = "$currentAccount/$RESULT_WATCH_STATE" - val folder2 = "$currentAccount/$RESULT_WATCH_STATE_DATA" - removeKeys(folder1) - removeKeys(folder2) - } - fun deleteBookmarkedData(id: Int?) { if (id == null) return + AccountManager.localListApi.requireLibraryRefresh = true removeKey("$currentAccount/$RESULT_WATCH_STATE", id.toString()) removeKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } From b2e0b7dec8fc76e5ed43a801c0718acc0ad6ce8b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:52:58 -0600 Subject: [PATCH 20/44] Fix "Multiple substitutions specified in non-positional format" (#727) --- app/src/main/res/values/strings.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f0e92d36..08ed6228 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,11 +77,11 @@ %d %.1f/10.0 %d - %s Ep %d + %1$s Ep %2$d Cast: %s Episode %d will be released in - %dd %dh %dm - %dh %dm + %1$dd %2$dh %3$dm + %1$dh %2$dm %dm Poster @@ -97,7 +97,7 @@ Speed (%.2fx) Rated: %.1f - New update found!\n%s -> %s + New update found!\n%1$s -> %2$s Filler %d min CloudStream @@ -276,12 +276,12 @@ developers Season - %s %d%s + %1$s %2$d%3$s No Season Episode Episodes - %d-%d - %d %s + %1$d-%2$d + %1$d %2$s S E No Episodes found @@ -471,7 +471,7 @@ Kitsu Trakt --> - %s %s + %1$s %2$s account Log out Log in @@ -572,8 +572,8 @@ Plugin Deleted Could not load %s 18+ - Started downloading %d %s… - Downloaded %d %s + Started downloading %1$d %2$s… + Downloaded %1$d %2$s All %s already downloaded No plugins found in repository Repository not found, check the URL and try VPN @@ -699,16 +699,16 @@ Add Replace Replace All - @string/cancel + @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\n%s \n\nWould you like to add this item anyway, replace the existing ones, or cancel the action? From 137d833d4ae20d6a4e04bcaabe246c49963c71ad Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:55:49 +0000 Subject: [PATCH 21/44] Improved Simkl autosync and fixed syncing seasons (#722) * Improved simkl autosync and fixed syncing seasons --- .../com/lagradost/cloudstream3/MainAPI.kt | 21 ++++++++ .../syncproviders/providers/SimklApi.kt | 54 +++++++++++-------- .../cloudstream3/ui/player/GeneratorPlayer.kt | 2 +- .../cloudstream3/ui/result/ResultFragment.kt | 8 ++- .../ui/result/ResultViewModel2.kt | 22 ++++++-- 5 files changed, 76 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index bfe86224..35a628a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -1465,6 +1465,15 @@ interface EpisodeResponse { var nextAiring: NextAiring? var seasonNames: List? fun getLatestEpisodes(): Map + + /** Count all episodes in all previous seasons up until this episode to get a total count. + * Example: + * Season 1: 10 episodes. + * Season 2: 6 episodes. + * + * getTotalEpisodeIndex(episode = 3, season = 2) -> 10 + 3 = 13 + * */ + fun getTotalEpisodeIndex(episode: Int, season: Int): Int } @JvmName("addSeasonNamesString") @@ -1544,6 +1553,12 @@ data class AnimeLoadResponse( .takeUnless { it == Int.MIN_VALUE } }.toMap() } + + override fun getTotalEpisodeIndex(episode: Int, season: Int): Int { + return this.episodes.maxOf { (_, episodes) -> + episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 } + } + episode + } } /** @@ -1752,6 +1767,12 @@ data class TvSeriesLoadResponse( .takeUnless { it == Int.MIN_VALUE } return mapOf(DubStatus.None to max) } + + override fun getTotalEpisodeIndex(episode: Int, season: Int): Int { + return episodes.count { + (it.season ?: Int.MIN_VALUE) < season && it.season != 0 + } + episode + } } suspend fun MainAPI.newTvSeriesLoadResponse( 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 bd7979f5..3a37a228 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 @@ -376,6 +376,8 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { private var status: Int? = null, private var addEpisodes: Pair?, List?>? = null, private var removeEpisodes: Pair?, List?>? = null, + // Required for knowing if the status should be overwritten + private var onList: Boolean = false ) { fun interceptor(interceptor: Interceptor) = apply { this.interceptor = interceptor } fun apiUrl(url: String) = apply { this.url = url } @@ -387,6 +389,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { } fun status(newStatus: Int?, oldStatus: Int?) = apply { + onList = oldStatus != null // Only set status if its new if (newStatus != oldStatus) { this.status = newStatus @@ -412,6 +415,11 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { // Do not add episodes if there is no change if (newEpisodes > (oldEpisodes ?: 0)) { this.addEpisodes = getEpisodes(allEpisodes.take(newEpisodes)) + + // Set to watching if episodes are added and there is no current status + if (!onList) { + status = SimklListStatusType.Watching.value + } } if ((oldEpisodes ?: 0) > newEpisodes) { this.removeEpisodes = getEpisodes(allEpisodes.drop(newEpisodes)) @@ -431,6 +439,28 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { interceptor = interceptor ).isSuccessful } else { + val statusResponse = status?.let { setStatus -> + val newStatus = + SimklListStatusType.values() + .firstOrNull { it.value == setStatus }?.originalName + ?: SimklListStatusType.Watching.originalName!! + + app.post( + "${this.url}/sync/add-to-list", + json = StatusRequest( + shows = listOf( + StatusMediaObject( + null, + null, + ids, + newStatus, + ) + ), movies = emptyList() + ), + interceptor = interceptor + ).isSuccessful + } ?: true + val episodeRemovalResponse = removeEpisodes?.let { (seasons, episodes) -> app.post( "${this.url}/sync/history/remove", @@ -472,28 +502,6 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { true } - val statusResponse = status?.let { setStatus -> - val newStatus = - SimklListStatusType.values() - .firstOrNull { it.value == setStatus }?.originalName - ?: SimklListStatusType.Watching.originalName!! - - app.post( - "${this.url}/sync/add-to-list", - json = StatusRequest( - shows = listOf( - StatusMediaObject( - null, - null, - ids, - newStatus, - ) - ), movies = emptyList() - ), - interceptor = interceptor - ).isSuccessful - } ?: true - statusResponse && episodeRemovalResponse && historyResponse } } @@ -1051,4 +1059,4 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { return true } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 1c751897..7c8d975a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1023,7 +1023,7 @@ class GeneratorPlayer : FullScreenPlayer() { ctx.getString(R.string.episode_sync_enabled_key), true ) ) maxEpisodeSet = meta.episode - sync.modifyMaxEpisode(meta.episode) + sync.modifyMaxEpisode(meta.totalEpisodeIndex ?: meta.episode) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 7617bc11..a1574eec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -47,7 +47,9 @@ data class ResultEpisode( /** * Conveys if the episode itself is marked as watched **/ - val videoWatchState: VideoWatchState + val videoWatchState: VideoWatchState, + /** Sum of all previous season episode counts + episode */ + val totalEpisodeIndex: Int? = null, ) fun ResultEpisode.getRealPosition(): Long { @@ -82,6 +84,7 @@ fun buildResultEpisode( isFiller: Boolean? = null, tvType: TvType, parentId: Int, + totalEpisodeIndex: Int? = null, ): ResultEpisode { val posDur = getViewPos(id) val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None @@ -103,7 +106,8 @@ fun buildResultEpisode( isFiller, tvType, parentId, - videoWatchState + videoWatchState, + totalEpisodeIndex ) } 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 1631b706..d744fac5 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 @@ -2215,6 +2215,10 @@ class ResultViewModel2 : ViewModel() { val id = mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0) + + val totalIndex = + i.season?.let { season -> loadResponse.getTotalEpisodeIndex(episode, season) } + if (!existingEpisodes.contains(id)) { existingEpisodes.add(id) val seasonData = loadResponse.seasonNames.getSeason(i.season) @@ -2234,7 +2238,8 @@ class ResultViewModel2 : ViewModel() { i.description, fillers.getOrDefault(episode, false), loadResponse.type, - mainId + mainId, + totalIndex ) val season = eps.seasonIndex ?: 0 @@ -2263,6 +2268,9 @@ class ResultViewModel2 : ViewModel() { val seasonData = loadResponse.seasonNames.getSeason(episode.season) + val totalIndex = + episode.season?.let { season -> loadResponse.getTotalEpisodeIndex(episodeIndex, season) } + val ep = buildResultEpisode( loadResponse.name, @@ -2279,7 +2287,8 @@ class ResultViewModel2 : ViewModel() { episode.description, null, loadResponse.type, - mainId + mainId, + totalIndex ) val season = ep.seasonIndex ?: 0 @@ -2310,7 +2319,8 @@ class ResultViewModel2 : ViewModel() { null, null, loadResponse.type, - mainId + mainId, + null ) ) } @@ -2332,7 +2342,8 @@ class ResultViewModel2 : ViewModel() { null, null, loadResponse.type, - mainId + mainId, + null ) ) } @@ -2354,7 +2365,8 @@ class ResultViewModel2 : ViewModel() { null, null, loadResponse.type, - mainId + mainId, + null ) ) } From f0e429436f92855708cb4e22ac121c59c8732eff Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:20:04 -0600 Subject: [PATCH 22/44] Use TV results layout on emulator (#710) * Use TV results layout on emulator * Add subscription support to emulator * Update --- .../ui/result/ResultFragmentTv.kt | 45 ++++++++++++++++++- .../lagradost/cloudstream3/utils/AppUtils.kt | 2 +- .../main/res/layout/fragment_result_tv.xml | 15 ++++++- app/src/main/res/values/strings.xml | 2 + 4 files changed, 60 insertions(+), 4 deletions(-) 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 c13854e0..396ec863 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 @@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable +import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator @@ -37,6 +38,7 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchHelper +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isRtl @@ -287,7 +289,8 @@ class ResultFragmentTv : Fragment() { resultResumeSeries, resultPlayTrailer, resultBookmarkButton, - resultFavoriteButton + resultFavoriteButton, + resultSubscribeButton ) for (requestView in views) { if (!requestView.isVisible) continue @@ -429,6 +432,7 @@ class ResultFragmentTv : Fragment() { binding?.resultEpisodesShow, binding?.resultBookmarkButton, binding?.resultFavoriteButton, + binding?.resultSubscribeButton, ).firstOrNull { it?.isVisible == true } @@ -574,6 +578,45 @@ class ResultFragmentTv : Fragment() { } } + observeNullable(viewModel.subscribeStatus) { isSubscribed -> + binding?.resultSubscribeButton?.apply { + isVisible = isSubscribed != null && context.isEmulatorSettings() + if (isSubscribed == null) return@observeNullable + + val drawable = if (isSubscribed) { + R.drawable.ic_baseline_notifications_active_24 + } else { + R.drawable.baseline_notifications_none_24 + } + + val text = if (isSubscribed) { + R.string.action_unsubscribe + } else { + R.string.action_subscribe + } + + setIconResource(drawable) + setText(text) + setOnClickListener { + viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? -> + if (newStatus == null) return@toggleSubscriptionStatus + + 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) + } + } + } + } + observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 48917889..1be966b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -583,7 +583,7 @@ object AppUtils { //private val viewModel: ResultViewModel by activityViewModels() private fun getResultsId(): Int { - return if (isTrueTvSettings()) { + return if (isTvSettings()) { R.id.global_to_navigation_results_tv } else { R.id.global_to_navigation_results_phone diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 51add4cb..9311d549 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -325,19 +325,30 @@ https://developer.android.com/design/ui/tv/samples/jet-fit style="@style/ResultButtonTV" android:nextFocusRight="@id/result_description" android:nextFocusUp="@id/result_bookmark_button" - android:nextFocusDown="@id/result_episodes_show" + android:nextFocusDown="@id/result_subscribe_button" android:text="@string/action_add_to_favorites" android:visibility="visible" app:icon="@drawable/ic_baseline_favorite_border_24" /> + + Subscribed to %s Unsubscribed from %s Episode %d released! + Subscribe + Unsubscribe Profile %d Wi-Fi Mobile data From 87c5aada8f9915ebd7fe132fd2185a54c8cedbbc Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:35:48 -0600 Subject: [PATCH 23/44] Bump androidx.preference:preference-ktx (#734) --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efd98724..b82d26e1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -179,7 +179,7 @@ dependencies { // DONT UPDATE, WILL CRASH ANDROID TV ???? implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") - implementation("androidx.preference:preference-ktx:1.2.0") + implementation("androidx.preference:preference-ktx:1.2.1") implementation("com.github.bumptech.glide:glide:4.13.1") kapt("com.github.bumptech.glide:compiler:4.13.1") From a8fdf5e8f224f5bf22e467181ebb3d4bf992692c Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:55:50 +0530 Subject: [PATCH 24/44] remove useless parent (#735) --- app/src/main/res/layout/fragment_home_head.xml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/layout/fragment_home_head.xml b/app/src/main/res/layout/fragment_home_head.xml index 90386ccf..0edaf230 100644 --- a/app/src/main/res/layout/fragment_home_head.xml +++ b/app/src/main/res/layout/fragment_home_head.xml @@ -92,15 +92,7 @@ android:layout_height="100dp" android:layout_gravity="bottom" android:gravity="center" - android:orientation="vertical"> - - + android:orientation="horizontal"> - + From 65313b4579bf9aed14ee0541a9bb2df2ede5f8a1 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:32:01 -0600 Subject: [PATCH 25/44] Add account selection activity and support profile locks (#736) --- app/src/main/AndroidManifest.xml | 22 ++- .../cloudstream3/ui/WhoIsWatchingAdapter.kt | 8 +- .../cloudstream3/ui/account/AccountAdapter.kt | 64 ++++++++ .../cloudstream3/ui/account/AccountDialog.kt | 115 +++++++++++++++ .../ui/account/AccountSelectActivity.kt | 91 ++++++++++++ .../cloudstream3/utils/DataStoreHelper.kt | 139 +++++++++++++----- app/src/main/res/layout/account_list_item.xml | 56 +++++++ .../res/layout/activity_account_select.xml | 28 ++++ .../res/layout/activity_account_select_tv.xml | 27 ++++ app/src/main/res/layout/lock_pin_dialog.xml | 25 ++++ .../res/layout/who_is_watching_account.xml | 9 ++ .../layout/who_is_watching_account_edit.xml | 6 + app/src/main/res/values/strings.xml | 9 ++ 13 files changed, 553 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt create mode 100644 app/src/main/res/layout/account_list_item.xml create mode 100644 app/src/main/res/layout/activity_account_select.xml create mode 100644 app/src/main/res/layout/activity_account_select_tv.xml create mode 100644 app/src/main/res/layout/lock_pin_dialog.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e0d43338..453c1fae 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -96,12 +96,6 @@ android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true"> - - - - - - @@ -165,6 +159,22 @@ + + + + + + + + + + + + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt index 6b3090a9..c5c38dc0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt @@ -55,7 +55,6 @@ class WhoIsWatchingAdapter( editCallBack = editCallBack, ) - override fun onBindViewHolder(holder: WhoIsWatchingHolder, position: Int) = holder.bind(currentList.getOrNull(position)) @@ -70,10 +69,15 @@ class WhoIsWatchingAdapter( fun bind(card: DataStoreHelper.Account?) { when (binding) { is WhoIsWatchingAccountBinding -> binding.apply { - if(card == null) return@apply + if (card == null) return@apply outline.isVisible = card.keyIndex == DataStoreHelper.selectedKeyIndex profileText.text = card.name profileImageBackground.setImage(card.image) + + // Handle the lock indicator + val isLocked = card.lockPin != null + lockIcon.isVisible = isLocked + root.setOnClickListener { selectCallBack(card) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt new file mode 100644 index 00000000..72551199 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt @@ -0,0 +1,64 @@ +package com.lagradost.cloudstream3.ui.account + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.databinding.AccountListItemBinding +import com.lagradost.cloudstream3.ui.result.setImage +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.utils.DataStoreHelper + +class AccountAdapter( + private val accounts: List, + private val onItemClick: (DataStoreHelper.Account) -> Unit +) : RecyclerView.Adapter() { + + inner class AccountViewHolder(private val binding: AccountListItemBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(account: DataStoreHelper.Account) { + val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex + + binding.accountName.text = account.name + binding.accountImage.setImage(account.image) + binding.lockIcon.isVisible = account.lockPin != null + binding.outline.isVisible = isLastUsedAccount + + if (isTvSettings()) { + binding.root.isFocusableInTouchMode = true + if (isLastUsedAccount) { + binding.root.requestFocus() + } + } + + binding.root.setOnClickListener { + onItemClick(account) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { + val binding = AccountListItemBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + + if (isTvSettings()) { + val layoutParams = binding.root.layoutParams as RecyclerView.LayoutParams + val marginInDp = 5 // Set the margin to 5dp + val marginInPixels = (marginInDp * parent.resources.displayMetrics.density).toInt() + layoutParams.setMargins(marginInPixels, marginInPixels, marginInPixels, marginInPixels) + binding.root.layoutParams = layoutParams + } + + return AccountViewHolder(binding) + } + + override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { + holder.bind(accounts[position]) + } + + override fun getItemCount(): Int { + return accounts.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt new file mode 100644 index 00000000..dfd8831b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt @@ -0,0 +1,115 @@ +package com.lagradost.cloudstream3.ui.account + +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.LockPinDialogBinding +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe + +object AccountDialog { + // TODO add account creation dialog to allow creating accounts directly from AccountSelectActivity + + fun showPinInputDialog( + context: Context, + currentPin: String?, + editAccount: Boolean, + callback: (String?) -> Unit + ) { + fun TextView.visibleWithText(@StringRes textRes: Int) { + visibility = View.VISIBLE + setText(textRes) + } + + fun View.isVisible() = visibility == View.VISIBLE + + val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context)) + + val isPinSet = currentPin != null + val isNewPin = editAccount && !isPinSet + val isEditPin = editAccount && isPinSet + + val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin + + val dialog = AlertDialog.Builder(context, R.style.AlertDialogCustom) + .setView(binding.root) + .setTitle(titleRes) + .setNegativeButton(R.string.cancel) { _, _ -> + callback.invoke(null) + } + .setOnCancelListener { + callback.invoke(null) + } + .setOnDismissListener { + if (binding.pinEditTextError.isVisible()) { + callback.invoke(null) + } + } + .create() + + var isPinValid = false + + binding.pinEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val enteredPin = s.toString() + val isEnteredPinValid = enteredPin.length == 4 + + if (isEnteredPinValid) { + if (isPinSet) { + if (enteredPin != currentPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect) + binding.pinEditText.text = null + isPinValid = false + } else { + binding.pinEditTextError.visibility = View.GONE + isPinValid = true + + callback.invoke(enteredPin) + dialog.dismissSafe() + } + } else { + binding.pinEditTextError.visibility = View.GONE + isPinValid = true + } + } else if (isNewPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_length) + isPinValid = false + } + } + + override fun afterTextChanged(s: Editable?) {} + }) + + // Detect IME_ACTION_DONE + binding.pinEditText.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE && isPinValid) { + val enteredPin = binding.pinEditText.text.toString() + callback.invoke(enteredPin) + dialog.dismissSafe() + } + true + } + + // We don't want to accidentally have the dialog dismiss when clicking outside of it. + // That is what the cancel button is for. + dialog.setCanceledOnTouchOutside(false) + + dialog.show() + + // Auto focus on PIN input and show keyboard + binding.pinEditText.requestFocus() + binding.pinEditText.postDelayed({ + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(binding.pinEditText, InputMethodManager.SHOW_IMPLICIT) + }, 200) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt new file mode 100644 index 00000000..152c19dc --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -0,0 +1,91 @@ +package com.lagradost.cloudstream3.ui.account + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.CommonActivity +import com.lagradost.cloudstream3.CommonActivity.loadThemes +import com.lagradost.cloudstream3.MainActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding +import com.lagradost.cloudstream3.databinding.ActivityAccountSelectTvBinding +import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute + +class AccountSelectActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + CommonActivity.init(this) + loadThemes(this) + + window.navigationBarColor = colorFromAttribute(R.attr.primaryBlackBackground) + + val binding = if (isTvSettings()) { + ActivityAccountSelectTvBinding.inflate(layoutInflater) + } else ActivityAccountSelectBinding.inflate(layoutInflater) + + setContentView(binding.root) + + val recyclerView: RecyclerView = binding.root.findViewById(R.id.account_recycler_view) + + val accounts = getAccounts(this@AccountSelectActivity) + + // Don't show account selection if there is only + // one account that exists + if (accounts.count() <= 1) { + navigateToMainActivity() + return + } + + val adapter = AccountAdapter(accounts) { selectedAccount -> + // Handle the selected account + onAccountSelected(selectedAccount) + } + recyclerView.adapter = adapter + + recyclerView.layoutManager = if (isTvSettings()) { + LinearLayoutManager(this) + } else GridLayoutManager(this, 2) + } + + private fun onAccountSelected(selectedAccount: DataStoreHelper.Account) { + if (selectedAccount.lockPin != null) { + // The selected account has a PIN set, prompt the user to enter the PIN + showPinInputDialog(this@AccountSelectActivity, selectedAccount.lockPin, false) { pin -> + if (pin == null) return@showPinInputDialog + // Pin is correct, proceed to main activity + setAccount(selectedAccount) + navigateToMainActivity() + } + } else { + // No PIN set for the selected account, proceed to main activity + setAccount(selectedAccount) + navigateToMainActivity() + } + } + + private fun setAccount(account: DataStoreHelper.Account) { + // Don't reload if it is the same account + if (DataStoreHelper.selectedKeyIndex == account.keyIndex) { + return + } + + DataStoreHelper.selectedKeyIndex = account.keyIndex + + MainActivity.bookmarksUpdatedEvent(true) + MainActivity.reloadHomeEvent(true) + } + + private fun navigateToMainActivity() { + val mainIntent = Intent(this, MainActivity::class.java) + startActivity(mainIntent) + finish() // Finish the account selection activity + } +} \ No newline at end of file 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 444c2ab2..e687bcfb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -6,6 +6,7 @@ import android.text.Editable import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.material.bottomsheet.BottomSheetDialog @@ -26,8 +27,8 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WhoIsWatchingAdapter +import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.ui.result.setImage @@ -136,6 +137,8 @@ object DataStoreHelper { val customImage: String? = null, @JsonProperty("defaultImageIndex") val defaultImageIndex: Int, + @JsonProperty("lockPin") + val lockPin: String? = null, ) { val image: UiImage get() = customImage?.let { UiImage.Image(it) } ?: UiImage.Drawable( @@ -230,36 +233,86 @@ object DataStoreHelper { binding.profilePic.setImage(account.image) binding.profilePic.setOnClickListener { - // rolls the image forwards once + // Roll the image forwards once currentEditAccount = currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % profileImages.size) binding.profilePic.setImage(currentEditAccount.image) } binding.applyBtt.setOnClickListener { - val currentAccounts = accounts.toMutableList() - - val overrideIndex = - currentAccounts.indexOfFirst { it.keyIndex == currentEditAccount.keyIndex } - - // if an account is found that has the same keyIndex then override that one, if not then append it - if (overrideIndex != -1) { - currentAccounts[overrideIndex] = currentEditAccount + if (currentEditAccount.lockPin != null) { + // Ask for the current PIN + showPinInputDialog(context, currentEditAccount.lockPin, false) { pin -> + if (pin == null) return@showPinInputDialog + // PIN is correct, proceed to update the account + performAccountUpdate(currentEditAccount) + dialog.dismissSafe() + } } else { - currentAccounts.add(currentEditAccount) + // No lock PIN set, proceed to update the account + performAccountUpdate(currentEditAccount) + dialog.dismissSafe() } - - // Save the current homepage for new accounts - val currentHomePage = DataStoreHelper.currentHomePage - - // set the new default account as well as add the key for the new account - setAccount(currentEditAccount, false) - DataStoreHelper.currentHomePage = currentHomePage - - accounts = currentAccounts.toTypedArray() - - dialog.dismissSafe() } + + // Handle setting or changing the PIN + + if (currentEditAccount.keyIndex == getDefaultAccount(context).keyIndex) { + binding.lockProfileCheckbox.isVisible = false + if (currentEditAccount.lockPin != null) { + currentEditAccount = currentEditAccount.copy(lockPin = null) + } + } + + var canSetPin = true + + binding.lockProfileCheckbox.isChecked = currentEditAccount.lockPin != null + + binding.lockProfileCheckbox.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + if (canSetPin) { + showPinInputDialog(context, null, true) { pin -> + if (pin == null) { + binding.lockProfileCheckbox.isChecked = false + return@showPinInputDialog + } + + currentEditAccount = currentEditAccount.copy(lockPin = pin) + } + } + } else { + if (currentEditAccount.lockPin != null) { + // Ask for the current PIN + showPinInputDialog(context, currentEditAccount.lockPin, true) { pin -> + if (pin == null || pin != currentEditAccount.lockPin) { + canSetPin = false + binding.lockProfileCheckbox.isChecked = true + } else { + currentEditAccount = currentEditAccount.copy(lockPin = null) + } + } + } + } + } + + canSetPin = true + } + + private fun performAccountUpdate(account: Account) { + val currentAccounts = accounts.toMutableList() + + val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex } + + if (overrideIndex != -1) { + currentAccounts[overrideIndex] = account + } else { + currentAccounts.add(account) + } + + val currentHomePage = this.currentHomePage + setAccount(account, false) + this.currentHomePage = currentHomePage + accounts = currentAccounts.toTypedArray() } private fun getDefaultAccount(context: Context): Account { @@ -272,10 +325,18 @@ object DataStoreHelper { } } + fun getAccounts(context: Context): List { + return accounts.toMutableList().apply { + val item = getDefaultAccount(context) + remove(item) + add(0, item) + } + } + fun showWhoIsWatching(context: Context) { - val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate( - LayoutInflater.from(context) - ) + val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context)) + val builder = BottomSheetDialog(context) + builder.setContentView(binding.root) val showAccount = accounts.toMutableList().apply { val item = getDefaultAccount(context) @@ -283,22 +344,25 @@ object DataStoreHelper { add(0, item) } - val builder = - BottomSheetDialog(context) - builder.setContentView(binding.root) val accountName = context.getString(R.string.account) - binding.profilesRecyclerview.setLinearListLayout( - isHorizontal = true, - nextUp = FOCUS_SELF, - nextDown = FOCUS_SELF, - nextLeft = FOCUS_SELF, - nextRight = FOCUS_SELF - ) + binding.profilesRecyclerview.setLinearListLayout(isHorizontal = true) binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter( selectCallBack = { account -> - setAccount(account, true) - builder.dismissSafe() + // Check if the selected account has a lock PIN set + if (account.lockPin != null) { + // Prompt for the lock pin + showPinInputDialog(context, account.lockPin, false) { pin -> + if (pin == null) return@showPinInputDialog + // Pin is correct, unlock the profile + setAccount(account, true) + builder.dismissSafe() + } + } else { + // No lock PIN set, directly set the account + setAccount(account, true) + builder.dismissSafe() + } }, addAccountCallback = { val currentAccounts = accounts @@ -334,7 +398,6 @@ object DataStoreHelper { builder.show() } - data class PosDur( @JsonProperty("position") val position: Long, @JsonProperty("duration") val duration: Long diff --git a/app/src/main/res/layout/account_list_item.xml b/app/src/main/res/layout/account_list_item.xml new file mode 100644 index 00000000..3331b85b --- /dev/null +++ b/app/src/main/res/layout/account_list_item.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_account_select.xml b/app/src/main/res/layout/activity_account_select.xml new file mode 100644 index 00000000..9138f82d --- /dev/null +++ b/app/src/main/res/layout/activity_account_select.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_account_select_tv.xml b/app/src/main/res/layout/activity_account_select_tv.xml new file mode 100644 index 00000000..87340ad2 --- /dev/null +++ b/app/src/main/res/layout/activity_account_select_tv.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lock_pin_dialog.xml b/app/src/main/res/layout/lock_pin_dialog.xml new file mode 100644 index 00000000..db2af48e --- /dev/null +++ b/app/src/main/res/layout/lock_pin_dialog.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/who_is_watching_account.xml b/app/src/main/res/layout/who_is_watching_account.xml index 4970d004..8152ed27 100644 --- a/app/src/main/res/layout/who_is_watching_account.xml +++ b/app/src/main/res/layout/who_is_watching_account.xml @@ -35,6 +35,15 @@ android:background="@drawable/outline_card" android:visibility="gone" /> + + + + tv_no_focus_tag + + + Enter PIN + Enter Current PIN + Lock Profile + PIN + Incorrect PIN. Please try again. + PIN must be 4 characters + Select an Account From 8b73c35e43ecdd1f8658b3ee75552ded132aaa22 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 31 Oct 2023 00:34:01 +0100 Subject: [PATCH 26/44] faster account skip startup --- .../ui/account/AccountSelectActivity.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index 152c19dc..a2c34bf0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -22,6 +22,15 @@ class AccountSelectActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val accounts = getAccounts(this@AccountSelectActivity) + + // Don't show account selection if there is only + // one account that exists + if (accounts.count() <= 1) { + navigateToMainActivity() + return + } + CommonActivity.init(this) loadThemes(this) @@ -35,14 +44,6 @@ class AccountSelectActivity : AppCompatActivity() { val recyclerView: RecyclerView = binding.root.findViewById(R.id.account_recycler_view) - val accounts = getAccounts(this@AccountSelectActivity) - - // Don't show account selection if there is only - // one account that exists - if (accounts.count() <= 1) { - navigateToMainActivity() - return - } val adapter = AccountAdapter(accounts) { selectedAccount -> // Handle the selected account From 6ce9f29331db4ad553447b17b5108a78945ad6b9 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Thu, 2 Nov 2023 21:50:49 +0200 Subject: [PATCH 27/44] Fix settings top bar on TV (#738) --- .../cloudstream3/ui/settings/SettingsAccount.kt | 2 ++ .../cloudstream3/ui/settings/SettingsFragment.kt | 10 ++++++++++ .../cloudstream3/ui/settings/SettingsGeneral.kt | 3 ++- .../cloudstream3/ui/settings/SettingsPlayer.kt | 2 ++ .../cloudstream3/ui/settings/SettingsProviders.kt | 2 ++ .../lagradost/cloudstream3/ui/settings/SettingsUI.kt | 2 ++ .../cloudstream3/ui/settings/SettingsUpdates.kt | 2 ++ 7 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index b3225d5c..aa5a3182 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -248,6 +249,7 @@ class SettingsAccount : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_account) setPaddingBottom() + setToolBarScrollFlags() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index a4d19eba..cb48b086 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -12,10 +12,12 @@ import android.widget.ImageView import androidx.annotation.StringRes import androidx.core.view.children import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager +import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.MaterialToolbar import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.MainSettingsBinding @@ -54,7 +56,15 @@ class SettingsFragment : Fragment() { listView?.setPadding(0, 0, 0, 100.toPx) } } + fun PreferenceFragmentCompat.setToolBarScrollFlags() { + if (isTvSettings()) { + val settingsAppbar = view?.findViewById(R.id.settings_toolbar) + settingsAppbar?.updateLayoutParams { + scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL + } + } + } fun Fragment?.setUpToolbar(title: String) { if (this == null) return val settingsToolbar = view?.findViewById(R.id.settings_toolbar) ?: return diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 17efd276..224ca74a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.ui.EasterEggMonke import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog @@ -115,6 +116,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_general) setPaddingBottom() + setToolBarScrollFlags() } data class CustomSite( @@ -192,7 +194,6 @@ class SettingsGeneral : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - fun showAdd() { val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } } activity?.showDialog( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index e10a5a1a..3d0bcb1f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment @@ -23,6 +24,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_player) setPaddingBottom() + setToolBarScrollFlags() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { hideKeyboard() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index 7e57fc5b..7dc73a46 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog @@ -25,6 +26,7 @@ class SettingsProviders : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_providers) setPaddingBottom() + setToolBarScrollFlags() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index e2fd24ca..8c720313 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog @@ -23,6 +24,7 @@ class SettingsUI : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_ui) setPaddingBottom() + setToolBarScrollFlags() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 2f796801..9f72c1d5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.services.BackupWorkManager import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.BackupUtils import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt @@ -42,6 +43,7 @@ class SettingsUpdates : PreferenceFragmentCompat() { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_updates) setPaddingBottom() + setToolBarScrollFlags() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { From 199f5b3a9d23297cb8f772e102619339452e9ff2 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 2 Nov 2023 20:58:52 +0100 Subject: [PATCH 28/44] Translations update from Hosted Weblate (#681) Co-authored-by: Ahmed Abd El-Fattah Co-authored-by: Fjuro Co-authored-by: Fqwe1 Co-authored-by: Giuseppe Terrana Co-authored-by: Luna712 <142361265+Luna712@users.noreply.github.com> Co-authored-by: Rex_sa Co-authored-by: gallegonovato Co-authored-by: ngocanhtve --- app/src/main/res/values-ar/strings.xml | 50 ++++++++++++---- app/src/main/res/values-cs/strings.xml | 49 ++++++++++++---- app/src/main/res/values-es/strings.xml | 58 ++++++++++++++----- app/src/main/res/values-it/strings.xml | 34 +++++++++-- app/src/main/res/values-uk/strings.xml | 54 ++++++++++++----- app/src/main/res/values-vi/strings.xml | 33 ++++++++++- app/src/main/res/values/strings.xml | 6 +- .../metadata/android/es-ES/changelogs/2.txt | 1 + .../android/es-ES/full_description.txt | 10 ++++ .../android/es-ES/short_description.txt | 1 + fastlane/metadata/android/es-ES/title.txt | 1 + 11 files changed, 234 insertions(+), 63 deletions(-) create mode 100644 fastlane/metadata/android/es-ES/changelogs/2.txt create mode 100644 fastlane/metadata/android/es-ES/full_description.txt create mode 100644 fastlane/metadata/android/es-ES/short_description.txt create mode 100644 fastlane/metadata/android/es-ES/title.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index f73081f7..15d22412 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -12,8 +12,8 @@ سرعة (%.2fx) تقييم: %.1f - !تم العثور على تحديث جديد -\n%s -> %s + يوجد تحديث جديد! +\n%1$s -> %2$s %d دقيقة CloudStream تشغيل بواسطة CloudStream @@ -319,7 +319,7 @@ Kitsu Trakt --> - %s %s + %1$s %2$s حساب تسجيل الخروج تسجيل الدخول @@ -418,8 +418,8 @@ تم إزالة الإضافة تعذر التحميل %s 18+ - بدأ تنزيل %d %s … - تم التنزيل %d %s + بدأ تنزيل %1$d %2$s… + تم تنزيل %1$d %2$s جميع %s محملة بالفعل تحميل مكثف إضافة @@ -461,11 +461,11 @@ السجل عرض زر تخطي المقدمة/الخاتمة طاقم العمل: %s - %d يوم %d ساعة %d دقيقة - %d ساعة %d دقيقة + %1$d يوم %2$d ساعة %3$d دقيقة + %1$d ساعة %2$d دقيقة الفيلير فتح(تشغيل) - %s %d%s + %1$s %2$d%3$s المكونات الإضافية المحدثة %d VLC MPV @@ -482,7 +482,7 @@ علّمه كفيديو تمت مشاهدته نعم - %s الحلقة %d + %1$s الحلقة %2$d سيتم إصدار الحلقة %d في تعذر تثبيت الإصدار الجديد من التطبيق تثبيت الإضافة أولا @@ -493,8 +493,8 @@ ‌تنزيل تحديث التطبيق… ‏تثبيت تحديث التطبيق… %d دقيقة - %d-%d - %d %s + %1$d-%2$d + %1$d %2$s هل أنت متأكد أنك تريد الخروج؟ قم بتثبيت جميع المكونات الإضافية التي لم يتم تثبيتها بعد تلقائيا من المستودعات المضافة. مثبت الحزم @@ -584,4 +584,30 @@ المستودع لم يتم العثور عليه، تحقق من العنوان اوجرب شبكة افتراضية خاصة(vpn) لقد صوتت بالفعل معدل النسخ الإحتياطي - + تمت إزالة %s من المفضلة + المفضلة + تمت إضافة %s إلى المفضلة + احتمال وجود تكرارات في مكتبتك. +\n +\n%s +\n +\nهل تريد الاضافة على اي حال مستبدلاً النسخة الموجودة بالفعل, أم تفضل إلغاء العملية؟ + احتمال أن يكون موجود بالفعل + قفل الحساب + اضافة الى المفضلة + تبديل الكل + رقم PIN غير صحيح. برجاء المحاولة مرة اخرى. + إلغاء الاشتراك + رقم ال PIN يجب ان يكون 4 ارقام + استبدال + اضافة + إشترك + إزالة من المفضلة + اختار حساب + من الظاهر أن \"%1$s\" موجود بالفعل في مكتبتك. +\n +\nهل تريد الاضافة على أي حال مستبدلاً القديم أو إلغاء العملية؟ + ادخال ال PIN + PIN + أدخل ال PIN الحالي + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8bdc1a27..ae93803d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -2,7 +2,7 @@ - %s Ep %d + %1$s Ep %2$d Hrají: %s Plakát @@ -17,7 +17,7 @@ Rychlost (%.2fx) Hodnocení: %.1f Nalezena nová aktualizace! -\n%s -> %s +\n%1$s -> %2$s Výplň %d min CloudStream @@ -292,7 +292,7 @@ Kitsu Trakt --> - %s %s + %1$s %2$s účet Odhlásit se Přihlásit se @@ -410,17 +410,17 @@ Příliš mnoho textu. Nepodařilo se uložit do schránky. Ano Prohlížeč - %d-%d + %1$d-%2$d Knihovna Zobrazit plakáty z Kitsu Automaticky stahovat doplňky Znovu provést proces nastavení Instalátor APK - %d %s + %1$d %2$s Některé telefony nepodporují nový instalátor balíčků. Pokud se aktualizace nenainstalují, zkuste použít starší možnost. Mezipaměť Epizoda %d bude vydána za - %dh %dm + %1$dh %2$dm Přehrát přímý přenos Rozšíření Akce @@ -436,7 +436,7 @@ Co chcete vidět Doplněk stažen 18+ - Spuštěno stahování %d %s… + Spuštěno stahování %1$d %2$s… CloudStream nemá ve výchozím nastavení nainstalované žádné weby. Stránky je třeba nainstalovat z úložišť. \n \nKvůli nesmyslnému podání stížnosti DMCA společností Sky UK Limited 🤮 nemůžeme v aplikaci propojit stránky repozitářů. @@ -505,12 +505,12 @@ Aktualizace aplikace Hotovo Podporováno - %s %d%s + %1$s %2$d%3$s Živý přenos NSFW Rozšíření Přehrát trailer - %dd %dh %dm + %1$dd %2$dh %3$dm Zobrazit komunitní repozitáře Aktualizace zahájena Stream @@ -520,7 +520,7 @@ Referent Další Sledovat videa v těchto jazycích - Staženo %d %s + Staženo %1$d %2$s Všechny %s jsou již staženy Hromadné stahování doplněk @@ -575,4 +575,31 @@ V repozitáři nebyly nalezeny žádné doplňky Repozitář nenalezen, zkontrolujte adresu URL a zkuste použít VPN Již jste hlasovali - + %s odebráno z oblíbených + Oblíbené + %s přidáno do oblíbených + Ve vaší knihovně byl nalezen potenciální duplikát: +\n +\n%s +\n +\nChcete přesto přidat tuto položku, nahradit existující nebo zrušit akci\? + Frekvence záloh + Nalezena potenciální duplicita + Zamknout profil + Přidat do oblíbených + Nahradit vše + Nesprávný PIN. Zkuste to prosím znovu. + Zrušit odběr + PIN musí obsahovat 4 znaky + Nahradit + Přidat + Odebírat + Odebrat z oblíbených + Vyberte účet + Vypadá to, že ve vaší knihovně již existuje potenciální duplikát: „%1$s“. +\n +\nChcete přesto přidat tuto položku, nahradit existující nebo zrušit akci\? + Zadejte PIN + PIN + Zadejte současný PIN + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 69086840..e292e0ba 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4,10 +4,10 @@ Descargue la lista de sitios que quiera utilizar Descargado:%d Descargado - Descargado %d %s + Descargado %1$d %2$s Borrar repositorio El episodio %d se lanzará en - %dh %dm + %1$dh %2$dm %dm Poster Extensiones @@ -97,12 +97,12 @@ Poster Siguiente al azar Todos los Idiomas - Volver + Regresar Cambiar proveedor Vista previa del fondo Nota:%.1f - Nueva actualización encontrada! -\n%s -> %s + ¡Nueva actualización encontrada! +\n%1$s -> %2$s Descargar Pausar Descarga Formato de fuente @@ -110,8 +110,8 @@ Tamaño de Fuente Velocidad (%.2fx) Omitir carga - %s Ep %d - %dd %dh %dm + %1$s Ep. %2$d + %1$dd %2$dh %3$dm Elenco %s Relleno %d min @@ -145,7 +145,7 @@ Transmitir Torrent Fuentes Reintentar conexión… - Volver + Regresar Descargando Descarga pausada Descarga iniciada @@ -218,8 +218,8 @@ Reproducir Episodio Episodio Episodios - %d-%d - %d %s + %1$d-%2$d + %1$d %2$s E Falló la restauración de los datos desde el archivo %s Datos guardados @@ -233,7 +233,7 @@ Mostrar los resultados de la búsqueda por proveedor Solo envíar los datos si la App se cierra / falla inesperadamente No enviar datos - Mostrar Trailers (avances) + Mostrar los trailers Mostrar pósters de Kitsu Actualizar a las versiones preliminares Buscar actualizaciones preliminares (beta) en lugar de solo versiones completas (stable releases) @@ -249,7 +249,7 @@ Reiniciar a valores predefinidos Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores Temporada - %s %d%s + %1$s %2$d%3$s Ninguna Temporada T Borrar Archivo @@ -311,7 +311,7 @@ Cambiar cuenta Añadir cuenta Sincronizar - Calificación + Clasificado %s autenticado No se pudo autenticar a %s Recomendado @@ -345,7 +345,7 @@ Color primario Tema de la aplicación hola@mundo.com - %s %s + %1$s %2$s Todo 1000ms Sin retraso de subtítulos @@ -448,7 +448,7 @@ Hecho Plugin Cargado 18+ - Iniciada la descarga %d %s… + Comenzó la descarga de %1$d %2$s… Descarga por lotes plugin plugins @@ -552,4 +552,30 @@ Repositorio no encontrado, comprueba la URL y prueba la VPN Ya has votado Frecuencia de la copia de seguridad - + %s eliminado de favoritos + Favoritos + %s añadido a favoritos + Se han encontrado posibles elementos duplicados en su biblioteca: +\n +\n%s +\n +\n¿Desea añadir este elemento de todos modos, sustituir los existentes o cancelar la acción\? + Posible duplicado encontrado + Perfil de bloqueo + Añadido a favoritos + Sustituir todo + PIN incorrecto. Por favor, inténtelo de nuevo. + Cancelar la suscripción + El PIN debe tener 4 caracteres + Sustituir + Añadir + Suscríbase + Eliminar de favoritos + Seleccione una cuenta + Parece que ya existe un elemento potencialmente duplicado en su biblioteca: \'%s.\' +\n +\n¿Desea añadir este elemento de todos modos, sustituir el existente o cancelar la acción\? + Introducir el PIN + PIN + Introduzca el PIN actual + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 933ac77f..72db056c 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -20,7 +20,7 @@ Velocità (%.2fx) Valutato: %.1f Nuovo aggiornamento trovato! -\n%s -> %s +\n%1$s -> %2$s Filler %d min @@ -409,8 +409,8 @@ Plugin eliminato Impossibile caricare %s 18+ - Download iniziato %d %s… - Scaricato %d %s + Download iniziato %1$d %2$s… + Scaricato %1$d %2$s Tutti %s già scaricati Download in blocco plugin @@ -573,4 +573,30 @@ Seleziona la modalità per filtrare il download dei plugin Disabilita Hai già votato - + %s rimosso dai preferiti + Preferiti + %s aggiunto ai preferiti + Dei possibili duplicati sono stati trovati nella tua libreria: +\n +\n%s +\n +\nVorresti aggiungere l\'oggetto alla libreria comunque, rimpiazzare l\'esistente, o cancellare l\'azione\? + Frequenza di backup + Trovato Possibile Duplicato + Aggiungi ai preferiti + Rimpiazza tutti + PIN non corretto. Riprova. + Disiscriviti + Il PIN deve essere almeno di 4 caratteri + Rimpiazza + Aggiungi + Iscriviti + Rimuovi dai preferiti + Seleziona un Account + Sembra che un oggetto potenziale duplicato sia già presente nella tua libreria: \'%1$s.\' +\n +\nVorresti aggiungere l\'oggetto lo stesso, rimpiazzare l\'esistente, o cancellare l\'azione\? + Inserisci PIN + PIN + Inserisci PIN Corrente + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 57b128de..c53de272 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -9,16 +9,16 @@ Актори: %s Епізод %d вийде через Poster - %s Еп. %d - %dд %dгод %dхв - %dгод %dхв + %1$s Еп. %2$d + %1$dд %2$dгод %3$dхв + %1$dгод %2$dхв %dхв Головний постер Наступний випадковий Попередній перегляд фону Швидкість (%.2fx) Знайдено нове оновлення! -\n%s –> %s +\n%1$s –> %2$s Пошук Завантаження %d хв @@ -112,7 +112,7 @@ Переглянути файл Детальніше Фільтр закладок - Очистити + Очистити Налаштування субтитрів Колір фону Висота субтитрів @@ -167,7 +167,7 @@ Скинути до значення за замовчуванням Немає сезону епізодів - %d %s + %1$d %2$s С Е Видалити файл @@ -238,9 +238,9 @@ Рік +30 Вибачте, у застосунку стався збій. Анонімне повідомлення про помилку буде відправлено розробникам - %s %d%s + %1$s %2$d%3$s Епізод - %d-%d + %1$d-%2$d Епізодів не знайдено Пауза Сезон @@ -364,7 +364,7 @@ Макет Постачальники example.com - %s %s + %2$s %1$s Депресивний обліковий запис Створити @@ -414,8 +414,8 @@ Плагін завантажено Плагін завантажено Не вдалося завантажити %s - Почалося завантаження %d %s… - Завантажено %d %s + Почалося завантаження %1$d %2$s… + Завантажено %1$d %2$s Всі %s вже завантажено Завантажити пакети плагін @@ -499,7 +499,7 @@ Ваша бібліотека порожня :( \nУвійдіть в обліковий запис бібліотеки або додайте фільми до вашої локальної бібліотеки. Алфавітом (від Я до А) - Виберіть бібліотеку + Оберіть бібліотеку Відкрити Браузер Цей список порожній. Спробуйте перейти до іншого. @@ -546,10 +546,36 @@ Якості Фон профілю Не вдалося створити UI коректно, це ВАЖЛИВА ПОМИЛКА, про яку слід негайно повідомити %s - Виберіть режим для фільтрації завантаження плагінів + Оберіть режим для фільтрації завантаження плагінів Вимкнути Репозиторій не знайдено, перевірте URL-адресу та спробуйте VPN Не знайдено жодних плагінів у репозиторії Ви вже проголосували Частота резервного копіювання - + %s вилучено з обраного + Обране + %s додано до обраного + У вашій бібліотеці знайдено потенційні дублікати: +\n +\n%s +\n +\nВсе одно хочете додати цей елемент, замінити наявні чи скасувати дію\? + Знайдено потенційний дублікат + Розблокувати профіль + Додати до обраного + Замінити усе + Неправильний PIN-код. Спробуйте ще раз. + Відписатись + PIN-код повинен складатися з 4 символів + Замінити + Додати + Підписатись + Вилучити з обраного + Оберіть обліковий запис + Виявилося, що у вашій бібліотеці вже є потенційно повторюваний елемент: \'%1$s.\' +\n +\nВсе одно хочете додати цей елемент, замінити наявний чи скасувати дію\? + Введіть PIN-код + PIN-код + Введіть поточний PIN-код + \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 8186d6ed..81575604 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -409,8 +409,8 @@ Plugin đã xoá Không tải được %s 18+ - Bắt đầu tải %d %s… - Tải xuống %d %s thành công + Đã bắt đầu tải xuống %1$d %2$s… + Đã tải xuống %1$d %2$s Toàn bộ %s đã được tải xuống Tải hàng loạt plugin @@ -566,4 +566,31 @@ Không tìm thấy plugin Không thể khởi tạo UI, đây là một LỖI LỚN và cần được báo cáo ngay lập tức tới %s Chọn chế độ để lọc plugin tải xuống - + %s đã loại bỏ khỏi mục yêu thích + Yêu thích + %s đã thêm vào mục yêu thích + Các mục có thể trùng lặp đã được tìm thấy trong thư viện của bạn: +\n +\n%s +\n +\nBạn vẫn muốn thêm mục này, thay thế những mục hiện có hay hủy hành động\? + Tần suất sao lưu + Đã tìm thấy bản sao tiềm năng + Khóa hồ sơ + Thêm vào mục yêu thích + Thay thế tất cả + Mã PIN không chính xác. Vui lòng thử lại. + Hủy đăng ký + Mã PIN phải có 4 ký tự + Thay thế + Thêm vào + Đăng ký + Loại bỏ khỏi mục yêu thích + Chọn một tài khoản + Có vẻ như một mục có khả năng trùng lặp đã tồn tại trong thư viện của bạn: \'%1$s.\' +\n +\nBạn vẫn muốn thêm mục này, thay thế mục hiện có hay hủy hành động\? + Nhập PIN + PIN + Nhập mã PIN hiện tại + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 351315f4..9e0575da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -701,9 +701,9 @@ Add Replace Replace All - @string/sort_cancel - - It appears that a potentially duplicate item already exists in your library: \'%1$s.\' + @string/cancel + + It appears that a potentially duplicate item already exists in your library: \'%s.\' \n\nWould you like to add this item anyway, replace the existing one, or cancel the action? diff --git a/fastlane/metadata/android/es-ES/changelogs/2.txt b/fastlane/metadata/android/es-ES/changelogs/2.txt new file mode 100644 index 00000000..68e0b99b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/2.txt @@ -0,0 +1 @@ +- ¡Cambios añadidos! diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt new file mode 100644 index 00000000..9f6accba --- /dev/null +++ b/fastlane/metadata/android/es-ES/full_description.txt @@ -0,0 +1,10 @@ +CloudStream-3 te permite ver y descargar películas, series de TV y anime. + +La aplicación viene sin ningún tipo de anuncios y análisis y +soporta múltiples tráilers y páginas de películas, y más, por ejemplo + +Marcadores + +Descargas de subtítulos + +Compatible con Chromecast diff --git a/fastlane/metadata/android/es-ES/short_description.txt b/fastlane/metadata/android/es-ES/short_description.txt new file mode 100644 index 00000000..eb1d11e8 --- /dev/null +++ b/fastlane/metadata/android/es-ES/short_description.txt @@ -0,0 +1 @@ +Vea y descargue películas, series de televisión y anime. diff --git a/fastlane/metadata/android/es-ES/title.txt b/fastlane/metadata/android/es-ES/title.txt new file mode 100644 index 00000000..dde89d58 --- /dev/null +++ b/fastlane/metadata/android/es-ES/title.txt @@ -0,0 +1 @@ +CloudStream From 6f40d2750f1ae5b04aa4c75a93e6ef1baedeea1b Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:59:11 +0000 Subject: [PATCH 29/44] chore(locales): fix locale issues --- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 15d22412..56e19280 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -610,4 +610,4 @@ ادخال ال PIN PIN أدخل ال PIN الحالي - \ No newline at end of file + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ae93803d..275553a9 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -602,4 +602,4 @@ Zadejte PIN PIN Zadejte současný PIN - \ No newline at end of file + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e292e0ba..32e2b61d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -578,4 +578,4 @@ Introducir el PIN PIN Introduzca el PIN actual - \ No newline at end of file + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 72db056c..d6be0eed 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -599,4 +599,4 @@ Inserisci PIN PIN Inserisci PIN Corrente - \ No newline at end of file + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c53de272..425c5257 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -578,4 +578,4 @@ Введіть PIN-код PIN-код Введіть поточний PIN-код - \ No newline at end of file + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 81575604..d27660c7 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -593,4 +593,4 @@ Nhập PIN PIN Nhập mã PIN hiện tại - \ No newline at end of file + From 908f83c50ea960b939ab2d7561a66eff180e0c6e Mon Sep 17 00:00:00 2001 From: KingLucius Date: Thu, 2 Nov 2023 22:03:00 +0200 Subject: [PATCH 30/44] Fix scroll for Library TV layout (#695) * Fix scroll for Library TV layout * Fixed without NestedScrollView --- .../ui/library/LibraryFragment.kt | 10 ++++++++ .../ui/library/ViewpagerAdapter.kt | 17 +++++++++++-- .../main/res/layout/fragment_library_tv.xml | 25 ++++++++++--------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index 7cc57f5d..864ca065 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -27,6 +27,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.viewpager2.widget.ViewPager2 import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.APIHolder @@ -137,6 +138,10 @@ class LibraryFragment : Fragment() { binding?.libraryRoot?.findViewById(R.id.search_src_text)?.apply { tag = "tv_no_focus_tag" + //Expand the Appbar when search bar is focused, fixing scroll up issue + setOnFocusChangeListener { _, _ -> + binding?.searchBar?.setExpanded(true) + } } // Set the color for the search exit icon to the correct theme text color @@ -342,6 +347,7 @@ class LibraryFragment : Fragment() { binding?.apply { viewpager.offscreenPageLimit = 2 viewpager.reduceDragSensitivity() + searchBar.setExpanded(true) } val startLoading = Runnable { @@ -441,6 +447,10 @@ class LibraryFragment : Fragment() { val distance = abs(position - currentItem) hideViewpager(distance) } + //Expand the appBar on tab focus + tab.view.setOnFocusChangeListener { view, b -> + binding?.searchBar?.setExpanded(true) + } }.attach() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 76028487..6731eae2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -1,14 +1,18 @@ package com.lagradost.cloudstream3.ui.library import android.os.Build +import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.doOnAttach import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.OnFlingListener +import com.google.android.material.appbar.AppBarLayout +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.search.SearchClickCallback +import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount class ViewpagerAdapter( @@ -67,6 +71,17 @@ class ViewpagerAdapter( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> val diff = scrollY - oldScrollY + + //Expand the top Appbar based on scroll direction up/down, simulate phone behavior + if (SettingsFragment.isTvSettings()) { + binding.root.rootView.findViewById(R.id.search_bar) + .apply { + if (diff <= 0) + setExpanded(true) + else + setExpanded(false) + } + } if (diff == 0) return@setOnScrollChangeListener scrollCallback.invoke(diff > 0) @@ -80,8 +95,6 @@ class ViewpagerAdapter( } } } - - } } diff --git a/app/src/main/res/layout/fragment_library_tv.xml b/app/src/main/res/layout/fragment_library_tv.xml index 22b9feb1..6d2198e9 100644 --- a/app/src/main/res/layout/fragment_library_tv.xml +++ b/app/src/main/res/layout/fragment_library_tv.xml @@ -27,7 +27,8 @@ android:id="@+id/search_status_bar_padding" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> + android:orientation="horizontal" + app:layout_scrollFlags="scroll|enterAlways"> + - + - Date: Fri, 3 Nov 2023 01:37:34 +0530 Subject: [PATCH 31/44] bump navigation lib (#749) --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b82d26e1..e0e4bb1d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -164,8 +164,8 @@ dependencies { implementation("com.google.android.material:material:1.10.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") - implementation("androidx.navigation:navigation-ui-ktx:2.7.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.5") + implementation("androidx.navigation:navigation-ui-ktx:2.7.5") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") From 5b0cbbf09f7632af4eea8c14265e2859d0cf6f6b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:14:16 -0600 Subject: [PATCH 32/44] Use nicer grid layout for account select on TV (#737) --- .../cloudstream3/ui/account/AccountAdapter.kt | 8 ------ .../ui/account/AccountSelectActivity.kt | 18 ++++++------- .../res/layout/activity_account_select.xml | 2 +- .../res/layout/activity_account_select_tv.xml | 27 ------------------- 4 files changed, 10 insertions(+), 45 deletions(-) delete mode 100644 app/src/main/res/layout/activity_account_select_tv.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt index 72551199..aea55392 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt @@ -43,14 +43,6 @@ class AccountAdapter( LayoutInflater.from(parent.context), parent, false ) - if (isTvSettings()) { - val layoutParams = binding.root.layoutParams as RecyclerView.LayoutParams - val marginInDp = 5 // Set the margin to 5dp - val marginInPixels = (marginInDp * parent.resources.displayMetrics.density).toInt() - layoutParams.setMargins(marginInPixels, marginInPixels, marginInPixels, marginInPixels) - binding.root.layoutParams = layoutParams - } - return AccountViewHolder(binding) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index a2c34bf0..457d4b81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -4,14 +4,12 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding -import com.lagradost.cloudstream3.databinding.ActivityAccountSelectTvBinding import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper @@ -36,13 +34,11 @@ class AccountSelectActivity : AppCompatActivity() { window.navigationBarColor = colorFromAttribute(R.attr.primaryBlackBackground) - val binding = if (isTvSettings()) { - ActivityAccountSelectTvBinding.inflate(layoutInflater) - } else ActivityAccountSelectBinding.inflate(layoutInflater) + val binding = ActivityAccountSelectBinding.inflate(layoutInflater) setContentView(binding.root) - val recyclerView: RecyclerView = binding.root.findViewById(R.id.account_recycler_view) + val recyclerView: RecyclerView = binding.accountRecyclerView val adapter = AccountAdapter(accounts) { selectedAccount -> @@ -51,9 +47,13 @@ class AccountSelectActivity : AppCompatActivity() { } recyclerView.adapter = adapter - recyclerView.layoutManager = if (isTvSettings()) { - LinearLayoutManager(this) - } else GridLayoutManager(this, 2) + if (isTvSettings()) { + val spanSize = if (accounts.count() <= 6) { + accounts.count() + } else 6 + + recyclerView.layoutManager = GridLayoutManager(this, spanSize) + } } private fun onAccountSelected(selectedAccount: DataStoreHelper.Account) { diff --git a/app/src/main/res/layout/activity_account_select.xml b/app/src/main/res/layout/activity_account_select.xml index 9138f82d..d5870f24 100644 --- a/app/src/main/res/layout/activity_account_select.xml +++ b/app/src/main/res/layout/activity_account_select.xml @@ -17,7 +17,7 @@ android:layout_marginTop="16dp" android:layout_marginBottom="16dp" /> - - - - - - - - - \ No newline at end of file From a6786aaf9860df0992e704bc3b3f1bed50360203 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:28:25 -0600 Subject: [PATCH 33/44] Add done button for when creating new PINs (#742) * Add done button for when creating new PINs * Cleanup --- .../cloudstream3/ui/account/AccountDialog.kt | 101 ++++++++++-------- 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt index dfd8831b..76686aef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt @@ -1,18 +1,17 @@ package com.lagradost.cloudstream3.ui.account import android.content.Context -import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater -import android.view.View import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager import android.widget.TextView import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.LockPinDialogBinding import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod object AccountDialog { // TODO add account creation dialog to allow creating accounts directly from AccountSelectActivity @@ -21,14 +20,18 @@ object AccountDialog { context: Context, currentPin: String?, editAccount: Boolean, + errorText: String? = null, callback: (String?) -> Unit ) { fun TextView.visibleWithText(@StringRes textRes: Int) { - visibility = View.VISIBLE + isVisible = true setText(textRes) } - fun View.isVisible() = visibility == View.VISIBLE + fun TextView.visibleWithText(text: String?) { + isVisible = true + setText(text) + } val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context)) @@ -38,7 +41,9 @@ object AccountDialog { val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin - val dialog = AlertDialog.Builder(context, R.style.AlertDialogCustom) + var isPinValid = false + + val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom) .setView(binding.root) .setTitle(titleRes) .setNegativeButton(R.string.cancel) { _, _ -> @@ -48,46 +53,59 @@ object AccountDialog { callback.invoke(null) } .setOnDismissListener { - if (binding.pinEditTextError.isVisible()) { + if (!isPinValid) { callback.invoke(null) } } - .create() - var isPinValid = false - - binding.pinEditText.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - val enteredPin = s.toString() - val isEnteredPinValid = enteredPin.length == 4 - - if (isEnteredPinValid) { - if (isPinSet) { - if (enteredPin != currentPin) { - binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect) - binding.pinEditText.text = null - isPinValid = false - } else { - binding.pinEditTextError.visibility = View.GONE - isPinValid = true - - callback.invoke(enteredPin) - dialog.dismissSafe() - } - } else { - binding.pinEditTextError.visibility = View.GONE - isPinValid = true - } - } else if (isNewPin) { - binding.pinEditTextError.visibleWithText(R.string.pin_error_length) - isPinValid = false + if (isNewPin) { + if (errorText != null) binding.pinEditTextError.visibleWithText(errorText) + builder.setPositiveButton(R.string.setup_done) { _, _ -> + if (!isPinValid) { + // If the done button is pressed and there is an error, + // ask again, and mention the error that caused this. + showPinInputDialog( + context = binding.root.context, + currentPin = null, + editAccount = true, + errorText = binding.pinEditTextError.text.toString(), + callback = callback + ) + } else { + val enteredPin = binding.pinEditText.text.toString() + callback.invoke(enteredPin) } } + } - override fun afterTextChanged(s: Editable?) {} - }) + val dialog = builder.create() + + binding.pinEditText.doOnTextChanged { text, _, _, _ -> + val enteredPin = text.toString() + val isEnteredPinValid = enteredPin.length == 4 + + if (isEnteredPinValid) { + if (isPinSet) { + if (enteredPin != currentPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect) + binding.pinEditText.text = null + isPinValid = false + } else { + binding.pinEditTextError.isVisible = false + isPinValid = true + + callback.invoke(enteredPin) + dialog.dismissSafe() + } + } else { + binding.pinEditTextError.isVisible = false + isPinValid = true + } + } else if (isNewPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_length) + isPinValid = false + } + } // Detect IME_ACTION_DONE binding.pinEditText.setOnEditorActionListener { _, actionId, _ -> @@ -108,8 +126,7 @@ object AccountDialog { // Auto focus on PIN input and show keyboard binding.pinEditText.requestFocus() binding.pinEditText.postDelayed({ - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(binding.pinEditText, InputMethodManager.SHOW_IMPLICIT) + showInputMethod(binding.pinEditText) }, 200) } } \ No newline at end of file From 11136fe63d494ac800df3a52560237500ca024a3 Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Sun, 5 Nov 2023 22:33:11 +0000 Subject: [PATCH 34/44] Fix selecting sources in cast (#752) --- .../java/com/lagradost/cloudstream3/utils/ExtractorApi.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index d89e67fa..923c3531 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.utils import android.net.Uri +import com.fasterxml.jackson.annotation.JsonIgnore import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.USER_AGENT @@ -377,7 +378,8 @@ open class ExtractorLink constructor( ) : VideoDownloadManager.IDownloadableMinimum { val isM3u8 : Boolean get() = type == ExtractorLinkType.M3U8 val isDash : Boolean get() = type == ExtractorLinkType.DASH - + + @JsonIgnore fun getAllHeaders() : Map { if (referer.isBlank()) { return headers @@ -920,4 +922,4 @@ abstract class ExtractorApi { open fun getExtractorUrl(id: String): String { return id } -} \ No newline at end of file +} From 22a0c25d8309097efbe92ac6af945e8438307b60 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:28:27 +0700 Subject: [PATCH 35/44] extractor: fixed Rabbitstream (#757) * Extractor: added Rabbitstream * Extractor: added Rabbitstream * fixed Rabbitstream --------- Co-authored-by: Sofie99 --- .../cloudstream3/extractors/Rabbitstream.kt | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index 0154b4e8..d5b52dd7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -16,13 +16,13 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec -// No License found in https://github.com/enimax-anime/key -// special credits to @enimax for providing key +// Code found in https://github.com/theonlymo/keys +// special credits to @theonlymo for providing key class Megacloud : Rabbitstream() { override val name = "Megacloud" override val mainUrl = "https://megacloud.tv" override val embed = "embed-2/ajax/e-1" - override val key = "https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt" + override val key = "https://raw.githubusercontent.com/theonlymo/keys/e1/key" } class Dokicloud : Rabbitstream() { @@ -35,7 +35,7 @@ open class Rabbitstream : ExtractorApi() { override val mainUrl = "https://rabbitstream.net" override val requiresReferer = false open val embed = "ajax/embed-4" - open val key = "https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt" + open val key = "https://raw.githubusercontent.com/theonlymo/keys/e4/key" override suspend fun getUrl( url: String, @@ -86,21 +86,23 @@ open class Rabbitstream : ExtractorApi() { private suspend fun getRawKey(): String = app.get(key).text - private fun extractRealKey(originalString: String?, stops: String): Pair { - val table = parseJson>>(stops) - val decryptedKey = StringBuilder() - var offset = 0 - var encryptedString = originalString + private fun extractRealKey(sources: String, stops: String): Pair { + val decryptKey = parseJson>>(stops) + val sourcesArray = sources.toCharArray() - table.forEach { (start, end) -> - decryptedKey.append(encryptedString?.substring(start - offset, end - offset)) - encryptedString = encryptedString?.substring( - 0, - start - offset - ) + encryptedString?.substring(end - offset) - offset += end - start + var extractedKey = "" + var currentIndex = 0 + for (index in decryptKey) { + val start = index[0] + currentIndex + val end = start + index[1] + for (i in start until end) { + extractedKey += sourcesArray[i].toString() + sourcesArray[i] = ' ' + } + currentIndex += index[1] } - return decryptedKey.toString() to encryptedString.toString() + + return extractedKey to sourcesArray.joinToString("") } private inline fun decryptMapped(input: String, key: String): T? { From 7e2908c0bbd3fd242cd4576b58039cfe01a378b0 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Fri, 10 Nov 2023 16:36:38 +0200 Subject: [PATCH 36/44] Fix top bar in Extensions & Test settings (#753) --- .../cloudstream3/ui/settings/SettingsFragment.kt | 9 +++++++++ .../ui/settings/extensions/ExtensionsFragment.kt | 3 ++- .../ui/settings/extensions/PluginsFragment.kt | 2 ++ .../cloudstream3/ui/settings/testing/TestFragment.kt | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index cb48b086..6ea6363e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -65,6 +65,15 @@ class SettingsFragment : Fragment() { } } } + fun Fragment?.setToolBarScrollFlags() { + if (isTvSettings()) { + val settingsAppbar = this?.view?.findViewById(R.id.settings_toolbar) + + settingsAppbar?.updateLayoutParams { + scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL + } + } + } fun Fragment?.setUpToolbar(title: String) { if (this == null) return val settingsToolbar = view?.findViewById(R.id.settings_toolbar) ?: return diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 553e7675..f0b8a0bd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus @@ -85,7 +86,7 @@ class ExtensionsFragment : Fragment() { //context?.fixPaddingStatusbar(extensions_root) setUpToolbar(R.string.extensions) - + setToolBarScrollFlags() binding?.repoRecyclerView?.apply { setLinearListLayout( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 172ea659..b490386f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog @@ -73,6 +74,7 @@ class PluginsFragment : Fragment() { return } + setToolBarScrollFlags() setUpToolbar(name) binding?.settingsToolbar?.apply { setOnMenuItemClickListener { menuItem -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt index 59b1b856..3fbd1131 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt @@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar @@ -27,6 +28,7 @@ class TestFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setUpToolbar(R.string.category_provider_test) + setToolBarScrollFlags() super.onViewCreated(view, savedInstanceState) binding?.apply { From c4aab5e5a827a051dd089c568dcb1d22d0215c42 Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:32:51 +0530 Subject: [PATCH 37/44] feat: make cloudstream fast boi, ksp migration (#689) * migrate from kapt to ksp * fook codefactor --- app/build.gradle.kts | 19 +++++++++++++------ .../lagradost/cloudstream3/AcraApplication.kt | 3 --- .../ui/settings/extensions/PluginAdapter.kt | 3 +-- .../lagradost/cloudstream3/utils/UIHelper.kt | 4 ++-- .../utils/VideoDownloadManager.kt | 2 +- build.gradle.kts | 4 ++++ 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e0e4bb1d..12859af5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,7 @@ import java.net.URL plugins { id("com.android.application") + id("com.google.devtools.ksp") id("kotlin-android") id("kotlin-kapt") id("org.jetbrains.dokka") @@ -87,6 +88,11 @@ android { ) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + ksp { + arg("room.schemaLocation", "$projectDir/schemas") + arg("exportSchema", "true") + } + kapt { includeCompileClasspath = true } @@ -181,9 +187,13 @@ dependencies { implementation("androidx.preference:preference-ktx:1.2.1") - implementation("com.github.bumptech.glide:glide:4.13.1") - kapt("com.github.bumptech.glide:compiler:4.13.1") - implementation("com.github.bumptech.glide:okhttp3-integration:4.13.0") + implementation("com.github.bumptech.glide:glide:4.15.1") + ksp("com.github.bumptech.glide:ksp:4.15.1") + implementation("com.github.bumptech.glide:okhttp3-integration:4.15.1") + // for ksp + ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") + implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0") + implementation("com.google.guava:guava:32.1.2-android") implementation("jp.wasabeef:glide-transformations:4.3.0") @@ -207,9 +217,6 @@ dependencies { implementation("ch.acra:acra-core:5.11.2") implementation("ch.acra:acra-toast:5.11.2") - compileOnly("com.google.auto.service:auto-service-annotations:1.1.1") - //either for java sources: - annotationProcessor("com.google.auto.service:auto-service:1.1.1") //or for kotlin sources (requires kapt gradle plugin): kapt("com.google.auto.service:auto-service:1.1.1") diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 5f3162b4..c93f0f9b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -8,7 +8,6 @@ import android.content.Intent import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import com.google.auto.service.AutoService import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.plugins.PluginManager @@ -37,7 +36,6 @@ import java.lang.ref.WeakReference import kotlin.concurrent.thread import kotlin.system.exitProcess - class CustomReportSender : ReportSender { // Sends all your crashes to google forms override fun send(context: Context, errorContent: CrashReportData) { @@ -65,7 +63,6 @@ class CustomReportSender : ReportSender { } } -@AutoService(ReportSenderFactory::class) class CustomSenderFactory : ReportSenderFactory { override fun create(context: Context, config: CoreConfiguration): ReportSender { return CustomReportSender() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index eb0082b8..c3fb4fc2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -21,7 +21,6 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.GlideApp import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.setImage @@ -87,7 +86,7 @@ class PluginAdapter( override fun onViewRecycled(holder: RecyclerView.ViewHolder) { if (holder is PluginViewHolder) { holder.binding.entryIcon.let { pluginIcon -> - GlideApp.with(pluginIcon).clear(pluginIcon) + com.bumptech.glide.Glide.with(pluginIcon).clear(pluginIcon) } } super.onViewRecycled(holder) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index d5357e0c..231a634d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -301,7 +301,7 @@ object UIHelper { } ?: return false return try { - var builder = GlideApp.with(this) + var builder = com.bumptech.glide.Glide.with(this) .load(glideImage) .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.ALL).let { req -> @@ -368,7 +368,7 @@ object UIHelper { ) { if (this == null || url.isNullOrBlank()) return try { - val res = GlideApp.with(this) + val res = com.bumptech.glide.Glide.with(this) .load(GlideUrl(url) { headers ?: emptyMap() }) .apply(bitmapTransform(BlurTransformation(radius, sample))) .transition( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index d108daed..50a8df02 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -234,7 +234,7 @@ object VideoDownloadManager { return cachedBitmaps[url] } - val bitmap = GlideApp.with(this) + val bitmap = com.bumptech.glide.Glide.with(this) .asBitmap() .load(GlideUrl(url) { headers ?: emptyMap() }) .into(720, 720) diff --git a/build.gradle.kts b/build.gradle.kts index 762e4588..5d7ea527 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,10 @@ allprojects { } } +plugins { + id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false +} + tasks.register("clean", Delete::class) { delete(rootProject.buildDir) } \ No newline at end of file From 3adf036135d7e4776a4cd8ac0aa1cb8a582a9c0a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:48:53 -0700 Subject: [PATCH 38/44] Fix some deprecations and other warnings (#750) * Fix some deprecations and other warnings --- app/src/main/AndroidManifest.xml | 16 ++-- .../lagradost/cloudstream3/MainActivity.kt | 75 ++++++++++++------- .../ui/download/DownloadChildFragment.kt | 6 +- .../ui/player/DownloadedPlayerActivity.kt | 14 +++- .../ui/result/ResultTrailerPlayer.kt | 37 ++++++--- .../ui/settings/SettingsFragment.kt | 4 +- .../ui/settings/extensions/PluginsFragment.kt | 4 +- .../cloudstream3/utils/IOnBackPressed.kt | 5 -- .../lagradost/cloudstream3/utils/UIHelper.kt | 4 +- .../main/res/xml/data_extraction_rules.xml | 3 + 10 files changed, 106 insertions(+), 62 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/IOnBackPressed.kt create mode 100644 app/src/main/res/xml/data_extraction_rules.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 453c1fae..a71c5ecb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ - + @@ -17,7 +17,11 @@ - + + + + tools:targetApi="tiramisu"> - + android:exported="false"> + diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index a41028bd..3a10aa10 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -19,6 +19,7 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResultLauncher import androidx.annotation.IdRes import androidx.annotation.MainThread @@ -132,7 +133,6 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.Event -import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState @@ -650,34 +650,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { builder.show().setDefaultFocus() } - private fun backPressed() { - this.window?.navigationBarColor = - this.colorFromAttribute(R.attr.primaryGrayBackground) - this.updateLocale() - this.updateLocale() - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment - val navController = navHostFragment?.navController - val isAtHome = - navController?.currentDestination?.matchDestination(R.id.navigation_home) == true - - if (isAtHome && isTvSettings()) { - showConfirmExitDialog() - } else { - super.onBackPressed() - } - } - - override fun onBackPressed() { - ((supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.childFragmentManager?.primaryNavigationFragment as? IOnBackPressed)?.onBackPressed() - ?.let { runNormal -> - if (runNormal) backPressed() - } ?: run { - backPressed() - } - } - override fun onDestroy() { val broadcastIntent = Intent() broadcastIntent.action = "restart_service" @@ -1087,6 +1059,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } catch (_: Throwable) { } } + override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -1384,6 +1357,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery) } } + + if (isTvSettings()) { + if (navDestination.matchDestination(R.id.navigation_home)) { + attachBackPressedCallback() + } else detachBackPressedCallback() + } } //val navController = findNavController(R.id.nav_host_fragment) @@ -1598,6 +1577,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // } // } + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + window?.navigationBarColor = colorFromAttribute(R.attr.primaryGrayBackground) + updateLocale() + + // If we don't disable we end up in a loop with default behavior calling + // this callback as well, so we disable it, run default behavior, + // then re-enable this callback so it can be used for next back press. + isEnabled = false + onBackPressedDispatcher.onBackPressed() + isEnabled = true + } + } + ) + } + + private var backPressedCallback: OnBackPressedCallback? = null + + private fun attachBackPressedCallback() { + if (backPressedCallback == null) { + backPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + showConfirmExitDialog() + window?.navigationBarColor = + colorFromAttribute(R.attr.primaryGrayBackground) + updateLocale() + } + } + } + + backPressedCallback?.isEnabled = true + onBackPressedDispatcher.addCallback(this, backPressedCallback ?: return) + } + + private fun detachBackPressedCallback() { + backPressedCallback?.isEnabled = false } suspend fun checkGithubConnectivity(): Boolean { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index f62482ed..c3ec2bbd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -60,7 +60,7 @@ class DownloadChildFragment : Fragment() { } }.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 } if (eps.isEmpty()) { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() return@main } @@ -78,7 +78,7 @@ class DownloadChildFragment : Fragment() { val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { - activity?.onBackPressed() // TODO FIX + activity?.onBackPressedDispatcher?.onBackPressed() // TODO FIX return } fixPaddingStatusbar(binding?.downloadChildRoot) @@ -87,7 +87,7 @@ class DownloadChildFragment : Fragment() { title = name setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt index 4c3376bb..1e2ea540 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.os.Bundle import android.util.Log import android.view.KeyEvent +import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R @@ -34,10 +35,6 @@ class DownloadedPlayerActivity : AppCompatActivity() { CommonActivity.onUserLeaveHint(this) } - override fun onBackPressed() { - finish() - } - private fun playLink(url: String) { this.navigate( R.id.global_to_navigation_player, GeneratorPlayer.newInstance( @@ -109,6 +106,15 @@ class DownloadedPlayerActivity : AppCompatActivity() { finish() return } + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onResume() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt index c30e70e5..ef3db0b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.activity.OnBackPressedCallback import androidx.core.view.isGone import androidx.core.view.isVisible import com.lagradost.cloudstream3.CommonActivity.screenHeight @@ -15,10 +16,8 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.PlayerEventSource import com.lagradost.cloudstream3.ui.player.SubtitleData -import com.lagradost.cloudstream3.utils.IOnBackPressed - -open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed { +open class ResultTrailerPlayer : ResultFragmentPhone() { override var lockRotation = false override var isFullScreenPlayer = false @@ -28,7 +27,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed { const val TAG = "RESULT_TRAILER" } - var playerWidthHeight: Pair? = null + private var playerWidthHeight: Pair? = null override fun nextEpisode() {} @@ -154,6 +153,10 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed { } fixPlayerSize() uiReset() + + if (isFullScreenPlayer) { + attachBackPressedCallback() + } else detachBackPressedCallback() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -172,12 +175,26 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed { } } - override fun onBackPressed(): Boolean { - return if (isFullScreenPlayer) { - updateFullscreen(false) - false - } else { - true + private var backPressedCallback: OnBackPressedCallback? = null + + private fun attachBackPressedCallback() { + if (backPressedCallback == null) { + backPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + updateFullscreen(false) + } + } } + + backPressedCallback?.isEnabled = true + + activity?.onBackPressedDispatcher?.addCallback( + activity ?: return, + backPressedCallback ?: return + ) + } + + private fun detachBackPressedCallback() { + backPressedCallback?.isEnabled = false } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 6ea6363e..37c71134 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -82,7 +82,7 @@ class SettingsFragment : Fragment() { setTitle(title) setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() } } fixPaddingStatusbar(settingsToolbar) @@ -97,7 +97,7 @@ class SettingsFragment : Fragment() { setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag) setNavigationOnClickListener { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() } } fixPaddingStatusbar(settingsToolbar) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index b490386f..c5256ffa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -70,7 +70,7 @@ class PluginsFragment : Fragment() { val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true if (url == null || name == null) { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() return } @@ -119,7 +119,7 @@ class PluginsFragment : Fragment() { if (searchView?.isIconified == false) { searchView.isIconified = true } else { - activity?.onBackPressed() + activity?.onBackPressedDispatcher?.onBackPressed() } } searchView?.setOnQueryTextFocusChangeListener { _, hasFocus -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/IOnBackPressed.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/IOnBackPressed.kt deleted file mode 100644 index b4922945..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/IOnBackPressed.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.lagradost.cloudstream3.utils - -interface IOnBackPressed { - fun onBackPressed(): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 231a634d..134d7127 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -418,7 +418,7 @@ object UIHelper { } fun FragmentActivity.popCurrentPage() { - this.onBackPressed() + this.onBackPressedDispatcher.onBackPressed() /*val currentFragment = supportFragmentManager.fragments.lastOrNull { it.isVisible } ?: return @@ -438,7 +438,7 @@ object UIHelper { val currentFragment = supportFragmentManager.fragments.lastOrNull { it.isVisible } - ?: //this.onBackPressed() + ?: //this.onBackPressedDispatcher.onBackPressed() return /* diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..ae9ece33 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 2b60e3a893416b2663e536b17ece81e12351bc11 Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Fri, 10 Nov 2023 23:49:37 +0000 Subject: [PATCH 39/44] Fix faulty automatic subtitle selection (#760) --- .../java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 8d1eb7df..03b89a68 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -1254,7 +1254,7 @@ class CS3IPlayer : IPlayer { .setMimeType(sub.mimeType) .setLanguage("_${sub.name}") .setId(sub.getId()) - .setSelectionFlags(SELECTION_FLAG_DEFAULT) + .setSelectionFlags(0) .build() when (sub.origin) { SubtitleOrigin.DOWNLOADED_FILE -> { From 6db295a799e0dfcc65f34c5b191a57d7f86cf016 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 11 Nov 2023 09:30:36 -0700 Subject: [PATCH 40/44] Upgrade gradle (#726) --- .idea/gradle.xml | 1 + app/build.gradle.kts | 16 ++++++++-------- build.gradle.kts | 9 ++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a8a2961a..c5c0ff3b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -8,6 +8,7 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/account_list_item.xml b/app/src/main/res/layout/account_list_item.xml index 3331b85b..f133d6c3 100644 --- a/app/src/main/res/layout/account_list_item.xml +++ b/app/src/main/res/layout/account_list_item.xml @@ -9,9 +9,9 @@ android:animateLayoutChanges="true" android:backgroundTint="?attr/primaryGrayBackground" android:foreground="?attr/selectableItemBackground" - app:cardCornerRadius="@dimen/rounded_image_radius" android:layout_margin="10dp" android:focusable="true" + app:cardCornerRadius="@dimen/rounded_image_radius" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1" app:layout_constraintEnd_toEndOf="parent" @@ -19,38 +19,38 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - + - + - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/who_is_watching_account_add.xml b/app/src/main/res/layout/account_list_item_add.xml similarity index 79% rename from app/src/main/res/layout/who_is_watching_account_add.xml rename to app/src/main/res/layout/account_list_item_add.xml index 91c7e419..dea64484 100644 --- a/app/src/main/res/layout/who_is_watching_account_add.xml +++ b/app/src/main/res/layout/account_list_item_add.xml @@ -2,16 +2,15 @@ + android:visibility="gone" + tools:visibility="visible" /> + android:visibility="gone" + tools:visibility="visible" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_account_select.xml b/app/src/main/res/layout/activity_account_select.xml index d5870f24..bd6007dc 100644 --- a/app/src/main/res/layout/activity_account_select.xml +++ b/app/src/main/res/layout/activity_account_select.xml @@ -1,28 +1,49 @@ - + android:layout_height="match_parent"> - + - + - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/who_is_watching.xml b/app/src/main/res/layout/who_is_watching.xml deleted file mode 100644 index f61cf6e4..00000000 --- a/app/src/main/res/layout/who_is_watching.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b349aecc..631201b1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -20,4 +20,6 @@ 50dp 1dp + + 100dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e0575da..ce660a67 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,6 +65,7 @@ filter_sub_lang_key pref_filter_search_quality_key enable_nsfw_on_providers_key + skip_startup_account_select_key enable_skip_op_from_database %d %s | %s @@ -720,10 +721,16 @@ Enter PIN + Enter PIN for %s Enter Current PIN Lock Profile PIN Incorrect PIN. Please try again. PIN must be 4 characters Select an Account + Manage Accounts + Edit account + Logged in as %s + Skip account selection at startup + Use Default Account diff --git a/app/src/main/res/xml/settings_account.xml b/app/src/main/res/xml/settings_account.xml index d3dbcb31..ec882088 100644 --- a/app/src/main/res/xml/settings_account.xml +++ b/app/src/main/res/xml/settings_account.xml @@ -1,6 +1,12 @@ + + From e11d36aed8b7452392259b8801537dd6cd91f332 Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Sun, 12 Nov 2023 15:36:21 +0000 Subject: [PATCH 44/44] Save selected subtitle language (#765) --- .../cloudstream3/ui/player/CS3IPlayer.kt | 3 +- .../ui/player/DownloadFileGenerator.kt | 3 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 43 +++++++++++++++---- .../ui/player/PlayerSubtitleHelper.kt | 7 ++- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 03b89a68..74ea71e7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -1014,7 +1014,8 @@ class CS3IPlayer : IPlayer { format.id!!, SubtitleOrigin.EMBEDDED_IN_VIDEO, format.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP, - emptyMap() + emptyMap(), + format.language ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index b0223bb5..5585924e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -100,7 +100,8 @@ class DownloadFileGenerator( uri.toString(), SubtitleOrigin.DOWNLOADED_FILE, name.toSubtitleMimeType(), - emptyMap() + emptyMap(), + null ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 7c8d975a..43e51e55 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -23,6 +23,7 @@ import androidx.media3.common.Format.NO_VALUE import androidx.media3.common.MimeTypes import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding @@ -38,6 +39,7 @@ import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -100,10 +102,33 @@ class GeneratorPlayer : FullScreenPlayer() { binding?.playerLoadingOverlay?.isVisible = true } - private fun setSubtitles(sub: SubtitleData?): Boolean { - currentSelectedSubtitles = sub - //Log.i(TAG, "setSubtitles = $sub") - return player.setPreferredSubtitles(sub) + private fun setSubtitles(subtitle: SubtitleData?): Boolean { + // If subtitle is changed -> Save the language + if (subtitle != currentSelectedSubtitles) { + val subtitleLanguage639 = if (subtitle == null) { + // "" is No Subtitles + "" + } else if (subtitle.languageCode != null) { + // Could be "English 4" which is why it is trimmed. + val trimmedLanguage = subtitle.languageCode.replace(Regex("\\d"), "").trim() + + languages.firstOrNull { language -> + language.languageName.equals(trimmedLanguage, ignoreCase = true) || + language.ISO_639_1 == subtitle.languageCode + }?.ISO_639_1 + } else { + null + } + + if (subtitleLanguage639 != null) { + setKey(SUBTITLE_AUTO_SELECT_KEY, subtitleLanguage639) + preferredAutoSelectSubtitles = subtitleLanguage639 + } + } + + currentSelectedSubtitles = subtitle + //Log.i(TAG, "setSubtitles = $subtitle") + return player.setPreferredSubtitles(subtitle) } override fun embeddedSubtitlesFetched(subtitles: List) { @@ -448,7 +473,8 @@ class GeneratorPlayer : FullScreenPlayer() { url = url, origin = SubtitleOrigin.URL, mimeType = url.toSubtitleMimeType(), - headers = currentSubtitle.headers + headers = currentSubtitle.headers, + currentSubtitle.lang ) runOnMainThread { addAndSelectSubtitles(subtitle) @@ -536,7 +562,8 @@ class GeneratorPlayer : FullScreenPlayer() { uri.toString(), SubtitleOrigin.DOWNLOADED_FILE, name.toSubtitleMimeType(), - emptyMap() + emptyMap(), + null ) addAndSelectSubtitles(subtitleData) @@ -946,7 +973,7 @@ class GeneratorPlayer : FullScreenPlayer() { var maxEpisodeSet: Int? = null var hasRequestedStamps: Boolean = false - override fun playerPositionChanged(position: Long, duration : Long) { + override fun playerPositionChanged(position: Long, duration: Long) { // Don't save livestream data if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return @@ -1209,7 +1236,7 @@ class GeneratorPlayer : FullScreenPlayer() { } } - override fun playerDimensionsLoaded(width: Int, height : Int) { + override fun playerDimensionsLoaded(width: Int, height: Int) { setPlayerDimen(width to height) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt index e532d1a3..25d7e3dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt @@ -30,13 +30,15 @@ enum class SubtitleOrigin { * @param name To be displayed in the player * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id * @param headers if empty it will use the base onlineDataSource headers else only the specified headers + * @param languageCode Not guaranteed to follow any standard. Could be something like "English 4" or "en". * */ data class SubtitleData( val name: String, val url: String, val origin: SubtitleOrigin, val mimeType: String, - val headers: Map + val headers: Map, + val languageCode: String? ) { /** Internal ID for exoplayer, unique for each link*/ fun getId(): String { @@ -80,7 +82,8 @@ class PlayerSubtitleHelper { url = subtitleFile.url, origin = SubtitleOrigin.URL, mimeType = subtitleFile.url.toSubtitleMimeType(), - headers = emptyMap() + headers = emptyMap(), + languageCode = subtitleFile.lang ) } }